Commits

Gora Khargosh  committed 9ef2c48

Mom.

Signed-off-by: Gora Khargosh <gora.khargosh@gmail.com>

  • Participants
  • Parent commits 15d26f4

Comments (0)

Files changed (22)

File mom/__init__.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Mother of all Python projects. Stuff that Python should have taken care of.
+#
+# Copyright (C) 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from mom import codec, security, types, builtins, decorators, math
+
+__all__ = [
+    "codec",
+    "security",
+    "types",
+    "builtins",
+    "decorators",
+    "math",
+]
+

File mom/_builtins.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Core builtins.
+#
+# Copyright (C) 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:module: mom._builtins
+:synopsis: Deals with a lot of cross-version issues.
+
+Should not be used in public code. Use the wrappers in mom.
+"""
+
+from __future__ import absolute_import
+
+try:
+    _BytesType = bytes
+except Exception:
+    _BytesType = str
+
+try:
+    # Not Python3
+    _UnicodeType = unicode
+    _BasestringType = basestring
+except Exception:
+    # Python3.
+    _UnicodeType = str
+    _BasestringType = (str, bytes)
+
+

File mom/builtins.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Facebook.
+# Copyright (C) 2010 Google Inc.
+# Copyright (C) 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:module: mom.builtins
+:synopsis: Deals with a lot of cross-version issues.
+
+``bytes``, ``str``, ``unicode``, and ``basestring`` mean different
+things to Python 2.5, 2.6, and 3.x.
+
+Use the functions provided instead of the built-in equivalents wherever
+possible. Avoid using ``str``—use ``bytes`` or ``unicode`` instead.
+
+Python 2.5
+* ``bytes`` is not available.
+* ``str`` is a byte string.
+* ``unicode`` converts to unicode string.
+* ``basestring`` exists.
+
+Python 2.6
+* ``bytes`` is available and maps to str
+* ``str`` is a byte string.
+* ``unicode`` converts to unicode string
+* ``basestring`` exists.
+
+Python 3.x
+* ``bytes`` is available and does not map to ``str``.
+* ``str`` maps to the earlier ``unicode``, but ``unicode`` has been removed.
+* ``basestring`` has been removed.
+* ``unicode`` has been removed
+
+This module adds portable support for all three versions
+of Python. It introduces these portable types that you can use
+in your code:
+
+* ``bytes`` where you need byte strings.
+* ``unicode`` where you need unicode strings
+* a few other utility functions that hide all the
+  complications behind type checking therefore cleaning
+  up the code base.
+
+Type detection
+--------------
+.. autofunction:: bytes
+.. autofunction:: unicode
+.. autofunction:: bin
+.. autofunction:: hex
+.. autofunction:: str
+.. autofunction:: byte_count
+.. autofunction:: bit_count
+.. autofunction:: is_python3
+.. autofunction:: is_sequence
+.. autofunction:: is_unicode
+.. autofunction:: is_bytes
+.. autofunction:: is_bytes_or_unicode
+"""
+
+from __future__ import absolute_import
+
+import sys
+import math
+
+from mom._builtins import _BytesType, _UnicodeType, _BasestringType
+
+
+def is_python3():
+    """
+    Determines whether we're running on Python 3.
+
+    :returns:
+        ``True`` if we're using Python 3; ``False`` otherwise.
+    """
+    return sys.version_info[0] == 3
+
+
+#def bytes(obj):
+#    """
+#    Converts an object value to a byte string.
+#
+#    :param obj:
+#        The object.
+#    :returns:
+#        A byte string representing the object.
+#    """
+#    return _BytesType(obj)
+
+
+#def unicode(obj):
+#    """
+#    Converts a Python object to a unicode string.
+#
+#    :param obj:
+#        The object
+#    :returns:
+#        Unicode string.
+#    """
+#    return _UnicodeType(obj)
+
+bytes = _BytesType
+unicode = _UnicodeType
+
+
+def str(_):
+    """
+    Reminds the developer to use ``bytes()`` or ``unicode()``.
+    """
+    raise TypeError("Please avoid using ``str()`` in your code. "\
+                    "Use ``bytes()`` or ``unicode()`` instead.")
+
+
+def bin(num, prefix="0b"):
+    """
+    Converts a long value to its binary representation.
+
+    :param num:
+        Long value.
+    :param prefix:
+        The prefix to use for the bitstring. Default "0b" to mimic Python
+        builtin ``bin()``.
+    :returns:
+        Bit string.
+    """
+    prefix = prefix or ""
+    bit_string = ''
+    while num > 1:
+        bit_string = bytes(num & 1) + bit_string
+        num >>= 1
+    bit_string = bytes(num) + bit_string
+    return prefix + bit_string
+
+
+def _bin_lookup(num, prefix="0b"):
+    """
+    Converts a long value to its binary representation based on a lookup table.
+
+    Alternative implementation of :func:``bin``.
+
+    :param num:
+        Long value.
+    :param prefix:
+        The prefix to use for the bitstring. Default "0b" to mimic Python
+        builtin ``bin()``.
+    :returns:
+        Bit string.
+    """
+    prefix = prefix or ""
+    bit_string = ''
+    lookup = {'0':'000','1':'001','2':'010','3':'011',
+              '4':'100','5':'101','6':'110','7':'111'}
+    for c in oct(num)[1:]:
+        bit_string += lookup[c]
+    return prefix + bit_string
+
+
+def _bin_recursive(num, prefix="0b"):
+    """
+    Converts a long value to its binary representation recursively.
+
+    Alternative implementation of :func:``bin``.
+
+    :param num:
+        Long value.
+    :param prefix:
+        The prefix to use for the bitstring. Default "0b" to mimic Python
+        builtin ``bin()``.
+    :returns:
+        Bit string.
+    """
+    prefix = prefix or ""
+    if num <= 1:
+        bitstring = bytes(num)
+    else:
+        bitstring = _bin_recursive(num >> 1) + bytes(num & 1)
+    return prefix + bitstring
+
+
+def hex(num, prefix="0x"):
+    """
+    Converts a long value to its hexadecimal representation.
+
+    :param num:
+        Long value.
+    :param prefix:
+        The prefix to use for the hexadecimal string. Default "0x" to mimic
+        ``hex()``.
+    :returns:
+        Hexadecimal string.
+    """
+    prefix = prefix or ""
+    hex_num = "%x" % num
+    return prefix + hex_num
+
+
+def byte_count(num):
+    """
+    Counts the number of bytes in a long integer.
+
+    :param num:
+        Long value.
+    :returns:
+        The number of bytes in the long integer.
+    """
+    if not num:
+        return 0
+    bits = bit_count(num)
+    return int(math.ceil(bits / 8.0))
+
+
+def bit_count(num):
+    """
+    Counts the number of bits in a long integer.
+
+    :param num:
+        Long value.
+    :returns:
+        Returns the number of bits in the long integer.
+    """
+    if not num:
+        return 0
+    hex_num = "%x" % num
+    return ((len(hex_num) - 1) * 4) + {
+        '0':0, '1':1, '2':2, '3':2,
+        '4':3, '5':3, '6':3, '7':3,
+        '8':4, '9':4, 'a':4, 'b':4,
+        'c':4, 'd':4, 'e':4, 'f':4,
+     }[hex_num[0]]
+    #return int(math.floor(math.log(n, 2))+1)
+
+
+def is_sequence(obj):
+    """
+    Determines whether the given value is a sequence.
+
+    :param obj:
+        The value to test.
+    :returns:
+        ``True`` if the value is a sequence; ``False`` otherwise.
+    """
+    try:
+        list(obj)
+        return True
+    except TypeError, exception:
+        assert "is not iterable" in bytes(exception)
+        return False
+
+
+def is_unicode(obj):
+    """
+    Determines whether the given value is a Unicode string.
+
+    :param obj:
+        The value to test.
+    :returns:
+        ``True`` if ``value`` is a Unicode string; ``False`` otherwise.
+    """
+    return isinstance(obj, _UnicodeType)
+
+
+def is_bytes(obj):
+    """
+    Determines whether the given value is a byte string.
+
+    :param obj:
+        The value to test.
+    :returns:
+        ``True`` if ``value`` is a byte string; ``False`` otherwise.
+    """
+    return isinstance(obj, _BytesType)
+
+
+def is_bytes_or_unicode(obj):
+    """
+    Determines whether the given value is an instance of a string irrespective
+    of whether it is a byte string or a Unicode string.
+
+    :param obj:
+        The value to test.
+    :returns:
+        ``True`` if ``value`` is a string; ``False`` otherwise.
+    """
+    return isinstance(obj, _BasestringType)
+
+
+def unicode_to_utf8(obj):
+    """
+    Converts a string argument to a UTF-8 encoded byte string if it is a
+    Unicode string.
+
+    :param obj:
+        If already a byte string or None, it is returned unchanged.
+        Otherwise it must be a Unicode string and is encoded as UTF-8.
+    """
+    if obj is None or is_bytes(obj):
+        return obj
+    assert is_unicode(obj)
+    return obj.encode("utf-8")
+
+
+def bytes_to_unicode(obj, encoding="utf-8"):
+    """
+    Converts bytes to a Unicode string decoding it according to the encoding
+    specified.
+
+    :param obj:
+        If already a Unicode string or None, it is returned unchanged.
+        Otherwise it must be a byte string.
+    :param encoding:
+        The encoding used to decode bytes. Defaults to UTF-8
+    """
+    if obj is None or is_unicode(obj):
+        return obj
+    assert is_bytes(obj)
+    return obj.decode(encoding)
+
+
+def to_utf8_if_unicode(obj):
+    """
+    Converts an argument to a UTF-8 encoded byte string if the argument
+    is a Unicode string.
+
+    :param obj:
+        The value that will be UTF-8 encoded if it is a Unicode string.
+    :returns:
+        UTF-8 encoded byte string if the argument is a Unicode string; otherwise
+        the value is returned unchanged.
+    """
+    return unicode_to_utf8(obj) if is_unicode(obj) else obj
+
+
+def to_unicode_if_bytes(obj, encoding="utf-8"):
+    """
+    Converts an argument to Unicode string if the argument is a byte string
+    decoding it as specified by the encoding.
+
+    :param obj:
+        The value that will be converted to a Unicode string.
+    :param encoding:
+        The encoding used to decode bytes. Defaults to UTF-8.
+    :returns:
+        Unicode string if the argument is a byte string. Otherwise the value
+        is returned unchanged.
+    """
+    return bytes_to_unicode(obj, encoding) if is_bytes(obj) else obj
+
+
+def to_unicode_recursively(obj):
+    """
+    Walks a simple data structure, converting byte strings to unicode.
+
+    Supports lists, tuples, and dictionaries.
+    """
+    if isinstance(obj, dict):
+        return dict((to_unicode_recursively(k),
+                     to_unicode_recursively(v)) for (k, v) in obj.iteritems())
+    elif isinstance(obj, list):
+        return list(to_unicode_recursively(i) for i in obj)
+    elif isinstance(obj, tuple):
+        return tuple(to_unicode_recursively(i) for i in obj)
+    elif is_bytes(obj):
+        return bytes_to_unicode(obj)
+    else:
+        return obj

File mom/codec/__init__.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005 Trevor Perrin <trevp@trevp.net>
+# Copyright (C) 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+:module: mom.codec
+:synopsis: Many different types of common encode/decode functions.
+
+Hexadecimal, base-64, binary, and decimal are byte string encodings.
+``bytearray`` is an array of bytes. This module contains codecs for
+converting between long, hex, base64, decimal, binary, mpi, and bytearray.
+
+.. autofunction:: base64_decode
+.. autofunction:: base64_encode
+
+.. autofunction:: base64_to_bytearray
+.. autofunction:: bytearray_to_base64
+
+.. autofunction:: bytes_to_hex
+.. autofunction:: hex_to_bytes
+
+.. autofunction:: bytes_to_base64
+.. autofunction:: base64_to_bytes
+
+.. autofunction:: bytes_to_decimal
+.. autofunction:: decimal_to_bytes
+
+.. autofunction:: bytes_to_long
+.. autofunction:: long_to_bytes
+
+.. autofunction:: bytes_to_bin
+.. autofunction:: bin_to_bytes
+
+.. autofunction:: base64_to_long
+.. autofunction:: long_to_base64
+
+.. autofunction:: bin_to_long
+.. autofunction:: long_to_bin
+
+.. autofunction:: hex_to_long
+.. autofunction:: long_to_hex
+
+.. autofunction:: mpi_to_long
+.. autofunction:: long_to_mpi
+"""
+
+import struct
+import binascii
+from mom.builtins import bytes, hex, bin, byte_count, bit_count
+from mom.types.bytearray import \
+    bytearray_concat, \
+    bytearray_create_zeros, \
+    bytes_to_bytearray, \
+    bytearray_to_bytes, \
+    long_to_bytearray, \
+    bytearray_to_long
+
+try:
+    # Check whether we have reduce as a built-in.
+    reduce_test = reduce((lambda num1, num2: num1 + num2), [1, 2, 3, 4])
+except NameError:
+    # Python 3k
+    from functools import reduce
+
+
+def base64_decode(encoded):
+    """
+    Decodes a base-64 encoded string into a byte string.
+
+    :param encoded:
+        Base-64 encoded byte string.
+    :returns:
+        byte string.
+    """
+    return binascii.a2b_base64(encoded)
+
+
+def base64_encode(byte_string):
+    """
+    Encodes a byte string using Base 64 and removes the last new line character.
+
+    :param byte_string:
+        The byte string to encode.
+    :returns:
+        Base64 encoded string without newline characters.
+    """
+    return binascii.b2a_base64(byte_string)[:-1]
+
+
+def bytes_to_hex(byte_string):
+    """
+    Converts a byte string to its hex representation.
+
+    :param byte_string:
+        Byte string.
+    :returns:
+        Hex-encoded byte string.
+    """
+    return binascii.b2a_hex(byte_string)
+
+
+def hex_to_bytes(encoded):
+    """
+    Converts a hex byte string to its byte representation.
+
+    :param encoded:
+        Hex string.
+    :returns:
+        Byte string.
+    """
+    return binascii.a2b_hex(encoded)
+
+
+def bytes_to_base64(byte_string):
+    """
+    Converts a byte string to its Base64 representation.
+    (Mostly for consistency.)
+
+    :param byte_string:
+        Byte string.
+    :returns:
+        Base64-encoded byte string.
+    """
+    return base64_encode(byte_string)
+
+
+def base64_to_bytes(encoded):
+    """
+    Decodes a base-64 encoded string into a byte string.
+
+    :param encoded:
+        Base-64 encoded byte string.
+    :returns:
+        byte string.
+    """
+    return base64_decode(encoded)
+
+
+def bytes_to_decimal(byte_string):
+    """
+    Converts a byte string to its decimal representation.
+
+    :param byte_string:
+        Byte string.
+    :returns:
+        Decimal-encoded byte string.
+    """
+    #return bytes(int(bytes_to_hex(byte_string), 16))
+    return bytes(bytes_to_long(byte_string))
+
+
+def decimal_to_bytes(encoded):
+    """
+    Converts a decimal encoded string to its byte representation.
+
+    :param encoded:
+        Decimal encoded string.
+    :returns:
+        Byte string.
+    """
+    return long_to_bytes(long(encoded))
+
+
+def long_to_base64(num):
+    """
+    Base-64 encodes a long.
+
+    :param num:
+        A long integer.
+    :returns:
+        Base-64 encoded byte string.
+    """
+    byte_string = long_to_bytes(num)
+    return base64_encode(byte_string)
+
+
+def base64_to_long(encoded):
+    """
+    Base-64 decodes a string into a long.
+
+    :param encoded:
+        The encoded byte string.
+    :returns:
+        Long value.
+    """
+    byte_string = base64_decode(encoded)
+    return bytes_to_long(byte_string)
+
+
+def base64_to_bytearray(encoded):
+    """
+    Converts a base-64 encoded value into a byte array.
+
+    :param encoded:
+        The base-64 encoded value.
+    :returns:
+        Byte array.
+    """
+    return bytes_to_bytearray(base64_decode(encoded))
+
+
+def bytearray_to_base64(byte_array):
+    """
+    Base-64 encodes a byte array.
+
+    :param byte_array:
+        The byte array.
+    :returns:
+        Base-64 encoded byte array without newlines.
+    """
+    return base64_encode(bytearray_to_bytes(byte_array))
+
+
+def long_to_hex(num):
+    """
+    Converts a long value to its hexadecimal representation without prefix.
+
+    :param num:
+        Long value.
+    :returns:
+        Hexadecimal string.
+    """
+    return hex(num, None)
+
+
+def hex_to_long(encoded):
+    """Convert a hex string to an integer.
+
+    The hex string can be any length. It can start with an 0x, or not.
+    Unrecognized characters will raise a ValueError.
+
+    This function released into the public domain by it's author, Lion
+    Kimbro.
+
+    :param encoded:
+        Hexadecimal string.
+    :returns:
+        Long value.
+    """
+    num = 0  # Resulting integer
+    encoded = encoded.lower()  # Hex string
+    if encoded[:2] == "0x":
+        encoded = encoded[2:]
+    for c in encoded:  # Hex character
+        num *= 16
+        if "0" <= c <= "9":
+            num += ord(c) - ord("0")
+        elif "a" <= c <= "f":
+            num += ord(c) - ord("a") + 10
+        else:
+            raise ValueError(c)
+    return num
+
+
+def long_to_bin(num):
+    """
+    Converts a long value to its binary representation without prefix.
+
+    :param num:
+        Long value.
+
+    :returns:
+        Bit string.
+    """
+    return bin(num, None)
+
+
+def bin_to_long(binary):
+    """
+    Converts a bit sequence to a long value.
+
+    :param binary:
+        Bit sequence.
+    :returns:
+        Long value.
+    """
+    return reduce((lambda first, second: (first << 1) + second), binary)
+
+
+# Taken from PyCrypto "as is".
+# Improved conversion functions contributed by Barry Warsaw, after
+# careful benchmarking
+
+def long_to_bytes(num, blocksize=0):
+    """
+    Convert a long integer to a byte string::
+
+        long_to_bytes(n:long, blocksize:int) : string
+
+    :param num:
+        Long value
+    :param blocksize:
+        If optional blocksize is given and greater than zero, pad the front of
+        the byte string with binary zeros so that the length is a multiple of
+        blocksize.
+    :returns:
+        Byte string.
+    """
+    # after much testing, this algorithm was deemed to be the fastest
+    s = ''
+    num = long(num)
+    pack = struct.pack
+    while num > 0:
+        s = pack('>I', num & 0xffffffffL) + s
+        num >>= 32
+    # strip off leading zeros
+    for i in range(len(s)):
+        if s[i] != '\000':
+            break
+    else:
+        # only happens when n == 0
+        s = '\000'
+        i = 0
+    s = s[i:]
+    # add back some pad bytes.  this could be done more efficiently w.r.t. the
+    # de-padding being done above, but sigh...
+    if blocksize > 0 and len(s) % blocksize:
+        s = (blocksize - len(s) % blocksize) * '\000' + s
+    return s
+
+
+def bytes_to_long(byte_string):
+    """
+    Convert a byte string to a long integer::
+
+        bytes_to_long(bytestring) : long
+
+    This is (essentially) the inverse of long_to_bytes().
+
+    :param byte_string:
+        A byte string.
+    :returns:
+        Long.
+    """
+    acc = 0L
+    unpack = struct.unpack
+    length = len(byte_string)
+    if length % 4:
+        extra = (4 - length % 4)
+        byte_string = '\000' * extra + byte_string
+        length = length + extra
+    for i in range(0, length, 4):
+        acc = (acc << 32) + unpack('>I', byte_string[i:i+4])[0]
+    return acc
+
+
+def bytes_to_bin(byte_string):
+    """
+    Converts a byte string to binary representation.
+
+    :param byte_string:
+        Byte string.
+    :returns:
+        Binary representation of the byte string.
+    """
+    return long_to_bin(bytes_to_long(byte_string))
+
+
+def bin_to_bytes(binary):
+    """
+    Converts a binary representation to bytes.
+
+    :param binary:
+        Binary representation.
+    :returns:
+        Byte string.
+    """
+    return long_to_bytes(bin_to_long(binary))
+
+
+def mpi_to_long(mpi_byte_string):
+    """
+    Converts an OpenSSL-format MPI Bignum byte string into a long.
+
+    :param mpi_byte_string:
+        OpenSSL-format MPI Bignum byte string.
+    :returns:
+        Long value.
+    """
+    #Make sure this is a positive number
+    assert (ord(mpi_byte_string[4]) & 0x80) == 0
+
+    byte_array = bytes_to_bytearray(mpi_byte_string[4:])
+    return bytearray_to_long(byte_array)
+
+
+def long_to_mpi(num):
+    """
+    Converts a long value into an OpenSSL-format MPI Bignum byte string.
+
+    :param num:
+        Long value.
+    :returns:
+        OpenSSL-format MPI Bignum byte string.
+    """
+    byte_array = long_to_bytearray(num)
+    ext = 0
+    #If the high-order bit is going to be set,
+    #add an extra byte of zeros
+    if not (bit_count(num) & 0x7):
+        ext = 1
+    length = byte_count(num) + ext
+    byte_array = bytearray_concat(bytearray_create_zeros(4+ext), byte_array)
+    byte_array[0] = (length >> 24) & 0xFF
+    byte_array[1] = (length >> 16) & 0xFF
+    byte_array[2] = (length >> 8) & 0xFF
+    byte_array[3] = length & 0xFF
+    return bytearray_to_bytes(byte_array)

File mom/codec/json.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Facebook.
+# Copyright (C) 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:module: mom.codec.json
+:synopsis: More portable JSON encoding and decoding routines.
+
+.. autofunction:: encode
+.. autofunction:: decode
+.. autofunction:: object_to_json
+.. autofunction:: json_to_object
+"""
+
+from __future__ import absolute_import
+from mom.builtins import to_unicode_recursively, bytes_to_unicode
+from mom.codec._json import json_dumps, json_loads
+
+try:
+    # Built-in JSON library.
+    import json
+    assert hasattr(json, "loads") and hasattr(json, "dumps")
+
+    def json_loads(value):
+        """Wrapper to decode JSON."""
+        print("System json")
+        return json.loads(value)
+    def json_dumps(value):
+        """Wrapper to encode JSON."""
+        print("System json")
+        return json.dumps(value)
+except Exception:
+    try:
+        # Try to use the simplejson library.
+        import simplejson as json
+        def json_loads(value):
+            """Wrapper to decode JSON."""
+            return json.loads(bytes_to_unicode(value))
+        def json_dumps(value):
+            """Wrapper to encode JSON."""
+            return json.dumps(value)
+    except ImportError:
+        try:
+            # For Google App Engine.
+            from django.utils import simplejson as json
+            def json_loads(value):
+                """Wrapper to decode JSON."""
+                return json.loads(bytes_to_unicode(value))
+            def json_dumps(value):
+                """Wrapper to encode JSON."""
+                return json.dumps(value)
+        except ImportError:
+            def json_loads(s):
+                """Wrapper to decode JSON."""
+                raise NotImplementedError(
+                    "A JSON parser is required, e.g., simplejson at "
+                    "http://pypi.python.org/pypi/simplejson/")
+            json_dumps = json_loads
+
+
+def encode(obj):
+    """
+    Encodes a Python value into its equivalent JSON string.
+
+    JSON permits but does not require forward slashes to be escaped.
+    This is useful when json data is emitted in a <script> tag
+    in HTML, as it prevents </script> tags from prematurely terminating
+    the javscript. Some json libraries do this escaping by default,
+    although python's standard library does not, so we do it here.
+
+    :see: http://stackoverflow.com/questions/1580647/json-why-are-forward-slashes-escaped
+    :param obj:
+        Python value.
+    :returns:
+        JSON string.
+    """
+    return json_dumps(to_unicode_recursively(obj)).replace("</", "<\\/")
+
+# Alias
+object_to_json = encode
+
+def decode(encoded):
+    """
+    Decodes a JSON string into its equivalent Python value.
+
+    :param encoded:
+        JSON string.
+    :returns:
+        Decoded Python value.
+    """
+    return json_loads(encoded)
+
+# Alias
+json_to_object = decode

File mom/decorators.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+:module: pyoauth.decorators
+:synopsis: Decorators used throughout the library.
+"""
+
+from __future__ import absolute_import
+
+import warnings
+import functools
+
+
+def deprecated(func):
+    """This is a decorator which can be used to mark functions
+    as deprecated. It will result in a warning being emitted
+    when the function is used.
+
+    ## Usage examples ##
+
+    ::
+        @deprecated
+        def my_func():
+            pass
+
+        @other_decorators_must_be_upper
+        @deprecated
+        def my_func():
+            pass
+    """
+    @functools.wraps(func)
+    def new_func(*args, **kwargs):
+        """
+        Wrapper function.
+        """
+        warnings.warn_explicit(
+            "Call to deprecated function %(funcname)s." % {
+                'funcname': func.__name__,
+            },
+            category=DeprecationWarning,
+            filename=func.func_code.co_filename,
+            lineno=func.func_code.co_firstlineno + 1
+        )
+        return func(*args, **kwargs)
+    return new_func
+
+
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005 Trevor Perrin <trevp@trevp.net>
+# Copyright (C) 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+:module: mom.math
+:synopsis: Math routines.
+
+Math
+----
+.. autofunction:: gcd
+.. autofunction:: lcm
+.. autofunction:: pow_mod
+.. autofunction:: inverse_mod
+
+Primes
+------
+.. autofunction:: is_prime
+.. autofunction:: generate_random_prime
+.. autofunction:: generate_random_safe_prime
+"""
+
+from __future__ import absolute_import
+
+import math
+from mom.security.random import generate_random_long
+
+
+def gcd(a, b):
+    """
+    Calculates the greatest common divisor.
+
+    Non-recursive fast implementation.
+
+    :param a:
+        Long value.
+    :param b:
+        Long value.
+    :returns:
+        Greatest common divisor.
+    """
+    a, b = max(a, b), min(a, b)
+    while b:
+        a, b = b, (a % b)
+    return a
+
+
+def lcm(a, b):
+    """
+    Least common multiple.
+
+    :param a:
+        Long value.
+    :param v:
+        Long value.
+    :returns:
+        Least common multiple.
+    """
+    return (a * b) // gcd(a, b)
+
+
+def inverse_mod(a, b):
+    """
+    Returns inverse of a mod b, zero if none
+
+    Uses Extended Euclidean Algorithm
+
+    :param a:
+        Long value
+    :param b:
+        Long value
+    :returns:
+        Inverse of a mod b, zero if none.
+    """
+    c, d = a, b
+    uc, ud = 1, 0
+    while c:
+        q = d // c
+        c, d = d - (q * c), c
+        uc, ud = ud - (q * uc), uc
+    if d == 1:
+        return ud % b
+    return 0
+
+
+try:
+    import gmpy
+    def pow_mod(base, power, modulus):
+        """
+        Calculates:
+            base**pow mod modulus
+
+        :param base:
+            Base
+        :param power:
+            Power
+        :param modulus:
+            Modulus
+        :returns:
+            base**pow mod modulus
+        """
+        base = gmpy.mpz(base)
+        power = gmpy.mpz(power)
+        modulus = gmpy.mpz(modulus)
+        result = pow(base, power, modulus)
+        return long(result)
+
+except ImportError:
+    def pow_mod(base, power, modulus):
+        """
+        Calculates:
+            base**pow mod modulus
+
+        Uses multi bit scanning with nBitScan bits at a time.
+        From Bryan G. Olson's post to comp.lang.python
+
+        Does left-to-right instead of pow()'s right-to-left,
+        thus about 30% faster than the python built-in with small bases
+
+        :param base:
+            Base
+        :param power:
+            Power
+        :param modulus:
+            Modulus
+        :returns:
+            base**pow mod modulus
+        """
+        nBitScan = 5
+
+        #TREV - Added support for negative exponents
+        negativeResult = False
+        if power < 0:
+            power *= -1
+            negativeResult = True
+
+        exp2 = 2**nBitScan
+        mask = exp2 - 1
+
+        # Break power into a list of digits of nBitScan bits.
+        # The list is recursive so easy to read in reverse direction.
+        nibbles = None
+        while power:
+            nibbles = int(power & mask), nibbles
+            power >>= nBitScan
+
+        # Make a table of powers of base up to 2**nBitScan - 1
+        lowPowers = [1]
+        for i in xrange(1, exp2):
+            lowPowers.append((lowPowers[i-1] * base) % modulus)
+
+        # To exponentiate by the first nibble, look it up in the table
+        nib, nibbles = nibbles
+        prod = lowPowers[nib]
+
+        # For the rest, square nBitScan times, then multiply by
+        # base^nibble
+        while nibbles:
+            nib, nibbles = nibbles
+            for i in xrange(nBitScan):
+                prod = (prod * prod) % modulus
+            if nib: prod = (prod * lowPowers[nib]) % modulus
+
+        #TREV - Added support for negative exponents
+        if negativeResult:
+            prodInv = inverse_mod(prod, modulus)
+            #Check to make sure the inverse is correct
+            assert (prod * prodInv) % modulus == 1
+            return prodInv
+        return prod
+
+
+def make_prime_sieve(size):
+    """
+    Pre-calculate a sieve of the ~100 primes < 1000.
+
+    :param size:
+        Count
+    :returns:
+        Prime sieve.
+    """
+    sieve = range(size)
+    for count in range(2, int(math.sqrt(size))):
+        if not sieve[count]:
+            continue
+        x = sieve[count] * 2
+        while x < len(sieve):
+            sieve[x] = 0
+            x += sieve[count]
+    sieve = [x for x in sieve[2:] if x]
+    return sieve
+
+sieve = make_prime_sieve(1000)
+
+
+def is_prime(num, iterations=5):
+    """
+    Determines whether a number is prime.
+
+    :param num:
+        Number
+    :param iterations:
+        Number of iterations.
+    :returns:
+        ``True`` if prime; ``False`` otherwise.
+    """
+    #Trial division with sieve
+    for x in sieve:
+        if x >= num:
+            return True
+        if not num % x:
+            return False
+    #Passed trial division, proceed to Rabin-Miller
+    #Rabin-Miller implemented per Ferguson & Schneier
+    #Compute s, t for Rabin-Miller
+    s, t = num-1, 0
+    while not s % 2:
+        s, t = s/2, t+1
+    #Repeat Rabin-Miller x times
+    a = 2 #Use 2 as a base for first iteration speedup, per HAC
+    for count in range(iterations):
+        v = pow_mod(a, s, num)
+        if v == 1:
+            continue
+        i = 0
+        while v != num-1:
+            if i == t-1:
+                return False
+            else:
+                v, i = pow_mod(v, 2, num), i+1
+        a = generate_random_long(2, num)
+    return True
+
+
+def generate_random_prime(bits):
+    """
+    Generates a random prime number.
+
+    :param bits:
+        Number of bits.
+    :return:
+        Prime number long value.
+    """
+    assert not bits < 10
+
+    #The 1.5 ensures the 2 MSBs are set
+    #Thus, when used for p,q in RSA, n will have its MSB set
+    #
+    #Since 30 is lcm(2,3,5), we'll set our test numbers to
+    #29 % 30 and keep them there
+    low = (2L ** (bits-1)) * 3/2
+    high = 2L ** bits - 30
+    p = generate_random_long(low, high)
+    p += 29 - (p % 30)
+    while 1:
+        p += 30
+        if p >= high:
+            p = generate_random_long(low, high)
+            p += 29 - (p % 30)
+        if is_prime(p):
+            return p
+
+
+def generate_random_safe_prime(bits):
+    """
+    Unused at the moment.
+
+    Generates a random prime number.
+
+    :param bits:
+        Number of bits.
+    :return:
+        Prime number long value.
+    """
+    assert not bits < 10
+
+    #The 1.5 ensures the 2 MSBs are set
+    #Thus, when used for p,q in RSA, n will have its MSB set
+    #
+    #Since 30 is lcm(2,3,5), we'll set our test numbers to
+    #29 % 30 and keep them there
+    low = (2 ** (bits-2)) * 3/2
+    high = (2 ** (bits-1)) - 30
+    q = generate_random_long(low, high)
+    q += 29 - (q % 30)
+    while 1:
+        q += 30
+        if q >= high:
+            q = generate_random_long(low, high)
+            q += 29 - (q % 30)
+        #Ideas from Tom Wu's SRP code
+        #Do trial division on p and q before Rabin-Miller
+        if is_prime(q, 0):
+            p = (2 * q) + 1
+            if is_prime(p):
+                if is_prime(q):
+                    return p

File mom/security/__init__.py

+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:module: mom.security
+:synopsis: Cryptography primitives.
+
+"""

File mom/security/codec/__init__.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+:module: mom.security.codec
+:synopsis: Codecs to encode and decode keys and certificates in various formats.
+
+PEM key decoders
+----------------
+.. autofunction:: public_key_pem_decode
+.. autofunction:: private_key_pem_decode
+
+"""
+
+from mom.security.codec.pem import \
+    CERT_PEM_HEADER, PUBLIC_KEY_PEM_HEADER, \
+    PRIVATE_KEY_PEM_HEADER, RSA_PRIVATE_KEY_PEM_HEADER
+from mom.security.codec.pem.x509 import X509Certificate
+from mom.security.codec.pem.rsa import RSAPrivateKey, RSAPublicKey
+
+
+def public_key_pem_decode(pem_key):
+    """
+    Decodes a PEM-encoded public key/X.509 certificate string into
+    internal representation.
+
+    :param pem_key:
+        The PEM-encoded key. Must be one of:
+        1. RSA public key.
+        2. X.509 certificate.
+    :returns:
+        A dictionary of key information.
+    """
+    pem_key = pem_key.strip()
+    if pem_key.startswith(CERT_PEM_HEADER):
+        key = X509Certificate(pem_key).public_key
+    elif pem_key.startswith(PUBLIC_KEY_PEM_HEADER):
+        key = RSAPublicKey(pem_key).public_key
+    else:
+        raise NotImplementedError(
+            "Only PEM X.509 certificates & public RSA keys can be read.")
+    return key
+
+
+def private_key_pem_decode(pem_key):
+    """
+    Decodes a PEM-encoded private key string into internal representation.
+
+    :param pem_key:
+        The PEM-encoded RSA private key.
+    :returns:
+        A dictionary of key information.
+    """
+    pem_key = pem_key.strip()
+    if pem_key.startswith(PRIVATE_KEY_PEM_HEADER) \
+    or pem_key.startswith(RSA_PRIVATE_KEY_PEM_HEADER):
+        key = RSAPrivateKey(pem_key).private_key
+    else:
+        raise NotImplementedError("Only PEM private RSA keys can be read.")
+    return key

File mom/security/codec/asn1/__init__.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:module: mom.security.codec.asn1
+:synopsis: Contains ASN.1 parsing routines to parse PEM-encoded keys.
+"""

File mom/security/codec/asn1/rsadsa.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:module: mom.security.codec.asn1.rsadsa
+:synopsis: ASN.1/DER decoding and encoding for RSA and DSA private keys.
+
+ASN.1 Syntax::
+
+   RSAPrivateKey ::= SEQUENCE {
+     version Version,
+     modulus INTEGER, -- n
+     publicExponent INTEGER, -- e
+     privateExponent INTEGER, -- d
+     prime1 INTEGER, -- p
+     prime2 INTEGER, -- q
+     exponent1 INTEGER, -- d mod (p-1)
+     exponent2 INTEGER, -- d mod (q-1)
+     coefficient INTEGER -- (inverse of q) mod p }
+
+   Version ::= INTEGER
+"""
+
+# Read unencrypted PKCS#1/PKIX-compliant, PEM & DER encoded private keys.
+# Private keys can be generated with "openssl genrsa|gendsa" commands.
+
+from __future__ import absolute_import
+from pyasn1.type import univ, namedtype, namedval, constraint
+
+class DSAPrivateKey(univ.Sequence):
+    """PKIX compliant DSA private key structure"""
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('version', univ.Integer(
+            namedValues=namedval.NamedValues(('v1', 0)))),
+        namedtype.NamedType('p', univ.Integer()),
+        namedtype.NamedType('q', univ.Integer()),
+        namedtype.NamedType('g', univ.Integer()),
+        namedtype.NamedType('public', univ.Integer()),
+        namedtype.NamedType('private', univ.Integer())
+        )
+
+MAX = 16
+
+class OtherPrimeInfo(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('prime', univ.Integer()),
+        namedtype.NamedType('exponent', univ.Integer()),
+        namedtype.NamedType('coefficient', univ.Integer())
+        )
+
+class OtherPrimeInfos(univ.SequenceOf):
+    componentType = OtherPrimeInfo()
+    subtypeSpec = univ.SequenceOf.subtypeSpec + \
+                  constraint.ValueSizeConstraint(1, MAX)
+
+class RSAPrivateKey(univ.Sequence):
+    """PKCS#1 compliant RSA private key structure"""
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('version', univ.Integer(
+            namedValues=namedval.NamedValues(('two-prime', 0), ('multi', 1)))),
+        namedtype.NamedType('modulus', univ.Integer()),
+        namedtype.NamedType('publicExponent', univ.Integer()),
+        namedtype.NamedType('privateExponent', univ.Integer()),
+        namedtype.NamedType('prime1', univ.Integer()),
+        namedtype.NamedType('prime2', univ.Integer()),
+        namedtype.NamedType('exponent1', univ.Integer()),
+        namedtype.NamedType('exponent2', univ.Integer()),
+        namedtype.NamedType('coefficient', univ.Integer()),
+        namedtype.OptionalNamedType('otherPrimeInfos', OtherPrimeInfos())
+        )

File mom/security/codec/asn1/x509.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+:module: mom.security.codec.asn1.x509
+:synopsis: ASN.1/DER decoding & encoding for X.509 certificates and public keys.
+
+X.509 Certificate parser.
+http://www.java2s.com/Open-Source/Python/Development/ASN.1-library-for-Python/pyasn1-0.0.11a/examples/x509.py.htm
+
+Basic Certification Fields (http://tools.ietf.org/html/rfc3280#section-4.1):
+----------------------------------------------------------------------------
+The X.509 v3 certificate basic syntax is as follows.  For signature
+calculation, the data that is to be signed is encoded using the ASN.1
+distinguished encoding rules (DER) [X.690].  ASN.1 DER encoding is a
+tag, length, value encoding system for each element.
+
+ASN.1 Syntax::
+
+    Certificate  ::=  SEQUENCE  {
+        tbsCertificate       TBSCertificate,
+        signatureAlgorithm   AlgorithmIdentifier,
+        signatureValue       BIT STRING  }
+
+    TBSCertificate  ::=  SEQUENCE  {
+        version         [0]  EXPLICIT Version DEFAULT v1,
+        serialNumber         CertificateSerialNumber,
+        signature            AlgorithmIdentifier,
+        issuer               Name,
+        validity             Validity,
+        subject              Name,
+        subjectPublicKeyInfo SubjectPublicKeyInfo,
+        issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
+                             -- If present, version MUST be v2 or v3
+        subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
+                             -- If present, version MUST be v2 or v3
+        extensions      [3]  EXPLICIT Extensions OPTIONAL
+                             -- If present, version MUST be v3
+        }
+
+    Version  ::=  INTEGER  {  v1(0), v2(1), v3(2)  }
+
+    CertificateSerialNumber  ::=  INTEGER
+
+    Validity ::= SEQUENCE {
+        notBefore      Time,
+        notAfter       Time }
+
+    Time ::= CHOICE {
+        utcTime        UTCTime,
+        generalTime    GeneralizedTime }
+
+    UniqueIdentifier  ::=  BIT STRING
+
+    SubjectPublicKeyInfo  ::=  SEQUENCE  {
+        algorithm            AlgorithmIdentifier,
+        subjectPublicKey     BIT STRING  }
+
+    Extensions  ::=  SEQUENCE SIZE (1..MAX) OF Extension
+
+    Extension  ::=  SEQUENCE  {
+        extnID      OBJECT IDENTIFIER,
+        critical    BOOLEAN DEFAULT FALSE,
+        extnValue   OCTET STRING  }
+"""
+
+from __future__ import absolute_import
+from pyasn1.type import tag, namedtype, namedval, univ, constraint, char, useful
+
+# Would be autogenerated from ASN.1 source by a ASN.1 parser
+# X.509 spec (rfc2459)
+
+MAX = 64  # TODO
+
+class DirectoryString(univ.Choice):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('teletexString',
+                            char.TeletexString().subtype(
+                                subtypeSpec=constraint.ValueSizeConstraint(
+                                    1, MAX))),
+        namedtype.NamedType('printableString',
+                            char.PrintableString().subtype(
+                                subtypeSpec=constraint.ValueSizeConstraint(
+                                    1, MAX))),
+        namedtype.NamedType('universalString',
+                            char.UniversalString().subtype(
+                                subtypeSpec=constraint.ValueSizeConstraint(
+                                    1, MAX))),
+        namedtype.NamedType('utf8String',
+                            char.UTF8String().subtype(
+                                subtypeSpec=constraint.ValueSizeConstraint(
+                                    1, MAX))),
+        namedtype.NamedType('bmpString',
+                            char.BMPString().subtype(
+                                subtypeSpec=constraint.ValueSizeConstraint(
+                                    1, MAX))),
+        namedtype.NamedType('ia5String',
+                            char.IA5String().subtype(
+                                subtypeSpec=constraint.ValueSizeConstraint(
+                                    1, MAX))) # hm, this should not be here!?
+        )
+
+class AttributeValue(DirectoryString): pass
+
+class AttributeType(univ.ObjectIdentifier): pass
+
+class AttributeTypeAndValue(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('type', AttributeType()),
+        namedtype.NamedType('value', AttributeValue())
+        )
+
+class RelativeDistinguishedName(univ.SetOf):
+    componentType = AttributeTypeAndValue()
+
+class RDNSequence(univ.SequenceOf):
+    componentType = RelativeDistinguishedName()
+
+class Name(univ.Choice):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('', RDNSequence())
+        )
+
+class AlgorithmIdentifier(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('algorithm', univ.ObjectIdentifier()),
+        namedtype.OptionalNamedType('parameters', univ.Null())
+        # TODO syntax screwed?
+#        namedtype.OptionalNamedType('parameters', univ.ObjectIdentifier())
+        )
+
+class Extension(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('extnID', univ.ObjectIdentifier()),
+        namedtype.DefaultedNamedType('critical', univ.Boolean('False')),
+        namedtype.NamedType('extnValue', univ.OctetString())
+        )
+
+class Extensions(univ.SequenceOf):
+    componentType = Extension()
+    sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)
+
+class SubjectPublicKeyInfo(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('algorithm', AlgorithmIdentifier()),
+        namedtype.NamedType('subjectPublicKey', univ.BitString())
+        )
+
+
+class UniqueIdentifier(univ.BitString): pass
+
+class Time(univ.Choice):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('utcTime', useful.UTCTime()),
+        namedtype.NamedType('generalTime', useful.GeneralizedTime())
+        )
+
+class Validity(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('notBefore', Time()),
+        namedtype.NamedType('notAfter', Time())
+        )
+
+class CertificateSerialNumber(univ.Integer): pass
+
+class Version(univ.Integer):
+    namedValues = namedval.NamedValues(
+        ('v1', 0), ('v2', 1), ('v3', 2)
+        )
+
+class TBSCertificate(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.DefaultedNamedType('version',
+                                     Version('v1',
+                                            tagSet=Version.tagSet.tagExplicitly(
+                                                 tag.Tag(
+                                                     tag.tagClassContext,
+                                                     tag.tagFormatSimple, 0)))),
+        namedtype.NamedType('serialNumber', CertificateSerialNumber()),
+        namedtype.NamedType('signature', AlgorithmIdentifier()),
+        namedtype.NamedType('issuer', Name()),
+        namedtype.NamedType('validity', Validity()),
+        namedtype.NamedType('subject', Name()),
+        namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()),
+        namedtype.OptionalNamedType('issuerUniqueID',
+                                    UniqueIdentifier().subtype(
+                                        implicitTag=tag.Tag(
+                                            tag.tagClassContext,
+                                            tag.tagFormatSimple, 1))),
+        namedtype.OptionalNamedType('subjectUniqueID',
+                                    UniqueIdentifier().subtype(
+                                        implicitTag=tag.Tag(
+                                            tag.tagClassContext,
+                                            tag.tagFormatSimple, 2))),
+        namedtype.OptionalNamedType('extensions',
+                                    Extensions().subtype(
+                                        explicitTag=tag.Tag(
+                                            tag.tagClassContext,
+                                            tag.tagFormatSimple, 3)))
+        )
+
+class Certificate(univ.Sequence):
+    componentType = namedtype.NamedTypes(
+        namedtype.NamedType('tbsCertificate', TBSCertificate()),
+        namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()),
+        namedtype.NamedType('signatureValue', univ.BitString())
+        )
+
+# end of ASN.1 data structures

File mom/security/codec/pem/__init__.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Written by Bill Janssen. Borrowed from the Python ``ssl`` module.
+
+"""
+:module: mom.security.codec.pem
+:synopsis: PEM/DER conversion utilities.
+
+PEM/DER codec
+-------------
+.. autofunction:: pem_to_der
+.. autofunction:: der_to_pem
+
+Miscellaneous
+-------------
+.. autofunction:: cert_time_to_seconds
+"""
+
+from __future__ import absolute_import
+
+import time
+import textwrap
+from functools import partial
+from mom.codec import base64_decode, base64_encode
+
+
+CERT_PEM_HEADER = '-----BEGIN CERTIFICATE-----'
+CERT_PEM_FOOTER = '-----END CERTIFICATE-----'
+
+PRIVATE_KEY_PEM_HEADER = '-----BEGIN PRIVATE KEY-----'
+PRIVATE_KEY_PEM_FOOTER = '-----END PRIVATE KEY-----'
+
+PUBLIC_KEY_PEM_HEADER = '-----BEGIN PUBLIC KEY-----'
+PUBLIC_KEY_PEM_FOOTER = '-----END PUBLIC KEY-----'
+
+RSA_PRIVATE_KEY_PEM_HEADER = '-----BEGIN RSA PRIVATE KEY-----'
+RSA_PRIVATE_KEY_PEM_FOOTER = '-----END RSA PRIVATE KEY-----'
+
+
+def cert_time_to_seconds(cert_time):
+    """
+    Takes a date-time string in standard ASN1_print form
+    ("MON DAY 24HOUR:MINUTE:SEC YEAR TIMEZONE") and return
+    a Python time value in seconds past the epoch.
+
+    :param cert_time:
+        Time value in the certificate.
+    :returns:
+        Python time value.
+    """
+    return time.mktime(time.strptime(cert_time, "%b %d %H:%M:%S %Y GMT"))
+
+
+def pem_to_der(pem_cert_string, pem_header, pem_footer):
+    """
+    Extracts the DER as a byte sequence out of an ASCII PEM formatted
+    certificate or key.
+
+    Taken from the Python SSL module.
+
+    :param pem_cert_string:
+        The PEM certificate or key string.
+    :param pem_header:
+        The PEM header to find.
+    :param pem_footer:
+        The PEM footer to find.
+    """
+    # Be a little lenient.
+    pem_cert_string = pem_cert_string.strip()
+    if not pem_cert_string.startswith(pem_header):
+        raise ValueError("Invalid PEM encoding; must start with %s"
+                         % pem_header)
+    if not pem_cert_string.endswith(pem_footer):
+        raise ValueError("Invalid PEM encoding; must end with %s"
+                         % pem_footer)
+    encoded = pem_cert_string[len(pem_header):-len(pem_footer)]
+    return base64_decode(encoded)
+
+
+def der_to_pem(der_cert_bytes, pem_header, pem_footer):
+    """
+    Takes a certificate in binary DER format and returns the
+    PEM version of it as a string.
+
+    Taken from the Python SSL module.
+
+    :param der_cert_bytes:
+        A byte string of the DER.
+    :param pem_header:
+        The PEM header to use.
+    :param pem_footer:
+        The PEM footer to use.
+    """
+    # Does what base64.b64encode without the `altchars` argument does.
+    encoded = base64_encode(der_cert_bytes)
+    return (pem_header + '\n' +
+            textwrap.fill(encoded, 64) + '\n' +
+            pem_footer + '\n')
+
+
+# Helper functions. Use these instead of using der_to_per and per_to_der.
+pem_to_der_private_key = partial(pem_to_der,
+                                 pem_header=PRIVATE_KEY_PEM_HEADER,
+                                 pem_footer=PRIVATE_KEY_PEM_FOOTER)
+pem_to_der_rsa_private_key = partial(pem_to_der,
+                                     pem_header=RSA_PRIVATE_KEY_PEM_HEADER,
+                                     pem_footer=RSA_PRIVATE_KEY_PEM_FOOTER)
+pem_to_der_public_key = partial(pem_to_der,
+                                pem_header=PUBLIC_KEY_PEM_HEADER,
+                                pem_footer=PUBLIC_KEY_PEM_FOOTER)
+pem_to_der_certificate = partial(pem_to_der,
+                                 pem_header=CERT_PEM_HEADER,
+                                 pem_footer=CERT_PEM_FOOTER)
+
+der_to_pem_private_key = partial(der_to_pem,
+                                 pem_header=PRIVATE_KEY_PEM_HEADER,
+                                 pem_footer=PRIVATE_KEY_PEM_FOOTER)
+der_to_pem_rsa_private_key = partial(der_to_pem,
+                                     pem_header=RSA_PRIVATE_KEY_PEM_HEADER,
+                                     pem_footer=RSA_PRIVATE_KEY_PEM_FOOTER)
+der_to_pem_public_key = partial(der_to_pem,
+                                pem_header=PUBLIC_KEY_PEM_HEADER,
+                                pem_footer=PUBLIC_KEY_PEM_FOOTER)
+der_to_pem_certificate = partial(der_to_pem,
+                                 pem_header=CERT_PEM_HEADER,
+                                 pem_footer=CERT_PEM_FOOTER)
+

File mom/security/codec/pem/rsa.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Yesudeep Mangalapilly <yesudeep@gmail.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+:module: mom.security.codec.pem.rsa
+:synopsis: RSA keys certificates parsing.
+
+.. autoclass:: RSAPublicKey
+.. autoclass:: RSAPrivateKey
+"""
+
+from __future__ import absolute_import
+
+from pyasn1.type import univ
+from pyasn1.codec.der import decoder, encoder
+from mom.builtins import bytes
+from mom.security.codec.asn1.x509 import SubjectPublicKeyInfo
+from mom.security.codec.asn1 import rsadsa
+from mom.security.codec.pem.x509 import X509Certificate
+from mom.security.codec.pem import \
+    pem_to_der_public_key, \
+    der_to_pem_public_key, \
+    der_to_pem_rsa_private_key, \
+    pem_to_der_rsa_private_key, \
+    pem_to_der_private_key
+
+
+class RSAPrivateKey(object):
+    """
+    ASN.1 Syntax::
+
+        RSAPrivateKey ::= SEQUENCE {
+          version Version,
+          modulus INTEGER, -- n
+          publicExponent INTEGER, -- e
+          privateExponent INTEGER, -- d
+          prime1 INTEGER, -- p
+          prime2 INTEGER, -- q
+          exponent1 INTEGER, -- d mod (p-1)
+          exponent2 INTEGER, -- d mod (q-1)
+          coefficient INTEGER -- (inverse of q) mod p }
+
+        Version ::= INTEGER
+    """
+    # http://tools.ietf.org/html/rfc3279 - Section 2.3.1
+    _RSA_OID = univ.ObjectIdentifier('1.2.840.113549.1.1.1')
+
+    def __init__(self, key):
+        self._key = key
+        self._key_asn1, self._private_key_asn1 = self.decode_from_pem_key(key)
+
+    def encode(self):
+        return self.encode_to_pem_private_key(self._key_asn1)
+
+    @property
+    def private_key(self):
+        asn = self._private_key_asn1
+        return dict(
+            version          = long(asn.getComponentByName('version')),
+            modulus          = long(asn.getComponentByName('modulus')),
+            publicExponent   = long(asn.getComponentByName('publicExponent')),
+            privateExponent  = long(asn.getComponentByName('privateExponent')),
+            prime1           = long(asn.getComponentByName('prime1')),
+            prime2           = long(asn.getComponentByName('prime2')),
+            exponent1        = long(asn.getComponentByName('exponent1')),
+            exponent2        = long(asn.getComponentByName('exponent2')),
+            coefficient      = long(asn.getComponentByName('coefficient')),
+        )
+
+
+    @classmethod
+    def decode_from_pem_key(cls, key):
+        keyType = rsadsa.RSAPrivateKey()
+        try:
+            der = pem_to_der_rsa_private_key(key)
+        except Exception, e:
+            #logging.exception(e)
+            der = pem_to_der_private_key(key)
+
+        cover_asn1 = decoder.decode(der)[0]
+        if len(cover_asn1) < 1:
+            raise ValueError("No RSA private key found after ASN.1 decoding.")
+
+        algorithm = cover_asn1[1][0]
+        if algorithm != cls._RSA_OID:
+            raise ValueError(
+                "Only RSA encryption is supported: got algorithm `%r`" \
+                % algorithm)
+        key_der = bytes(cover_asn1[2])
+        key_asn1 = decoder.decode(key_der, asn1Spec=keyType)[0]
+        return cover_asn1, key_asn1
+
+    @classmethod
+    def encode_to_pem_private_key(cls, key_asn1):
+        return der_to_pem_rsa_private_key(encoder.encode(key_asn1))
+
+TEST_RSA_PRIVATE_KEYS = (
+    '''
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V
+A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d
+7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ
+hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H
+X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm
+uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw
+rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z
+zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn
+qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG
+WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno
+cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+
+3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8
+AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54
+Lw03eHTNQghS0A==
+-----END PRIVATE KEY-----''',
+)
+TEST_PRIVATE_KEYS = (0
+    ,
+)
+
+class RSAPublicKey(object):
+    """
+    ASN.1 Syntax::
+
+        SubjectPublicKeyInfo  ::=  SEQUENCE  {
+            algorithm            AlgorithmIdentifier,
+            subjectPublicKey     BIT STRING  }
+    """
+    # http://tools.ietf.org/html/rfc3279 - Section 2.3.1
+    _RSA_OID = univ.ObjectIdentifier('1.2.840.113549.1.1.1')
+
+    def __init__(self, key):
+        self._key = key
+        self._key_asn1 = self.decode_from_pem_key(key)
+
+    def encode(self):
+        return self.encode_to_pem_key(self._key_asn1)
+
+    @property
+    def public_key(self):
+        algorithm = self._key_asn1.getComponentByName('algorithm')[0]
+        if algorithm != self._RSA_OID:
+            raise NotImplementedError(
+                "Only RSA encryption is supported: got algorithm `%r`" \
+                % algorithm)
+        modulus, exponent = self.parse_public_rsa_key_bits(
+            self._key_asn1.getComponentByName('subjectPublicKey'))
+        return dict(
+            modulus=modulus,
+            exponent=exponent,
+        )
+
+    @classmethod
+    def parse_public_rsa_key_bits(cls, public_key_bitstring):
+        """
+        Extracts the RSA modulus and exponent from a RSA public key bit string.
+
+        :param public_key_bitstring:
+            ASN.1 public key bit string.
+        :returns:
+            Tuple of (modulus, exponent)
+        """
+        return X509Certificate.parse_public_rsa_key_bits(public_key_bitstring)
+
+    @classmethod
+    def decode_from_pem_key(cls, key):
+        keyType = SubjectPublicKeyInfo()
+        der = pem_to_der_public_key(key)
+        key_asn1 = decoder.decode(der, asn1Spec=keyType)[0]
+        if len(key_asn1) < 1:
+            raise ValueError("No RSA public key found after ASN.1 decoding.")
+        return key_asn1
+
+    @classmethod
+    def encode_to_pem_key(cls, key_asn1):
+        return der_to_pem_public_key(encoder.encode(key_asn1))
+
+
+TEST_PUBLIC_PEM_KEYS = ("""
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0YjCwIfYoprq/FQO6lb3asXrx
+LlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlYzypSRjVxwxrsuRcP3e641SdASwfr
+mzyvIgP08N4S0IFzEURkV1wp/IpH7kH41EtbmUmrXSwfNZsnQRE5SYSOhh+LcK2w
+yQkdgcMv11l4KoBkcwIDAQAB
+-----END PUBLIC KEY-----
+""",
+    )
+