Learn more  » Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

aroundthecode / pycryptodome   python

Repository URL to install this package:

Version: 3.7.2 

/ Util / asn1.py

# -*- coding: ascii -*-
#
#  Util/asn1.py : Minimal support for ASN.1 DER binary encoding.
#
# ===================================================================
# The contents of this file are dedicated to the public domain.  To
# the extent that dedication to the public domain is not available,
# everyone is granted a worldwide, perpetual, royalty-free,
# non-exclusive license to exercise all rights associated with the
# contents of this file for any purpose whatsoever.
# No rights are reserved.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# ===================================================================

import struct

from Crypto.Util.py3compat import byte_string, b, bchr, bord

from Crypto.Util.number import long_to_bytes, bytes_to_long

__all__ = ['DerObject', 'DerInteger', 'DerOctetString', 'DerNull',
           'DerSequence', 'DerObjectId', 'DerBitString', 'DerSetOf']


def _is_number(x, only_non_negative=False):
    test = 0
    try:
        test = x + test
    except TypeError:
        return False
    return not only_non_negative or x >= 0


class BytesIO_EOF(object):
    """This class differs from BytesIO in that a ValueError exception is
    raised whenever EOF is reached."""

    def __init__(self, initial_bytes):
        self._buffer = initial_bytes
        self._index = 0
        self._bookmark  = None

    def set_bookmark(self):
        self._bookmark = self._index

    def data_since_bookmark(self):
        assert self._bookmark is not None
        return self._buffer[self._bookmark:self._index]

    def remaining_data(self):
        return len(self._buffer) - self._index

    def read(self, length):
        new_index = self._index + length
        if new_index > len(self._buffer):
            raise ValueError

        result = self._buffer[self._index:new_index]
        self._index = new_index
        return result

    def read_byte(self):
        return bord(self.read(1)[0])


class DerObject(object):
        """Base class for defining a single DER object.

        This class should never be directly instantiated.
        """

        def __init__(self, asn1Id=None, payload=b'', implicit=None,
                     constructed=False, explicit=None):
                """Initialize the DER object according to a specific ASN.1 type.

                :Parameters:
                  asn1Id : integer
                    The universal DER tag number for this object
                    (e.g. 0x10 for a SEQUENCE).
                    If None, the tag is not known yet.

                  payload : byte string
                    The initial payload of the object (that it,
                    the content octets).
                    If not specified, the payload is empty.

                  implicit : integer
                    The IMPLICIT tag number to use for the encoded object.
                    It overrides the universal tag *asn1Id*.

                  constructed : bool
                    True when the ASN.1 type is *constructed*.
                    False when it is *primitive*.

                  explicit : integer
                    The EXPLICIT tag number to use for the encoded object.
                """

                if asn1Id is None:
                    # The tag octet will be read in with ``decode``
                    self._tag_octet = None
                    return
                asn1Id = self._convertTag(asn1Id)

                self.payload = payload

                # In a BER/DER identifier octet:
                # * bits 4-0 contain the tag value
                # * bit 5 is set if the type is 'constructed'
                #   and unset if 'primitive'
                # * bits 7-6 depend on the encoding class
                #
                # Class        | Bit 7, Bit 6
                # ----------------------------------
                # universal    |   0      0
                # application  |   0      1
                # context-spec |   1      0 (default for IMPLICIT/EXPLICIT)
                # private      |   1      1
                #
                if None not in (explicit, implicit):
                    raise ValueError("Explicit and implicit tags are"
                                     " mutually exclusive")

                if implicit is not None:
                    self._tag_octet = 0x80 | 0x20 * constructed | self._convertTag(implicit)
                    return

                if explicit is not None:
                    self._tag_octet = 0xA0 | self._convertTag(explicit)
                    self._inner_tag_octet = 0x20 * constructed | asn1Id
                    return
                
                self._tag_octet = 0x20 * constructed | asn1Id

        def _convertTag(self, tag):
                """Check if *tag* is a real DER tag.
                Convert it from a character to number if necessary.
                """
                if not _is_number(tag):
                    if len(tag) == 1:
                        tag = bord(tag[0])
                # Ensure that tag is a low tag
                if not (_is_number(tag) and 0 <= tag < 0x1F):
                    raise ValueError("Wrong DER tag")
                return tag

        @staticmethod
        def _definite_form(length):
                """Build length octets according to BER/DER
                definite form.
                """
                if length > 127:
                        encoding = long_to_bytes(length)
                        return bchr(len(encoding) + 128) + encoding
                return bchr(length)

        def encode(self):
                """Return this DER element, fully encoded as a binary byte string."""

                # Concatenate identifier octets, length octets,
                # and contents octets

                output_payload = self.payload

                # In case of an EXTERNAL tag, first encode the inner
                # element.
                if hasattr(self, "_inner_tag_octet"):
                    output_payload = (bchr(self._inner_tag_octet) +
                                      self._definite_form(len(self.payload)) +
                                      self.payload)

                return (bchr(self._tag_octet) +
                        self._definite_form(len(output_payload)) +
                        output_payload)

        def _decodeLen(self, s):
                """Decode DER length octets from a file."""

                length = s.read_byte()
                if length <= 127:
                        return length
                payloadLength = bytes_to_long(s.read(length & 0x7F))
                # According to DER (but not BER) the long form is used
                # only when the length doesn't fit into 7 bits.
                if payloadLength <= 127:
                        raise ValueError("Not a DER length tag (but still valid BER).")
                return payloadLength

        def decode(self, der_encoded, strict=False):
                """Decode a complete DER element, and re-initializes this
                object with it.

                Args:
                  der_encoded (byte string): A complete DER element.

                Raises:
                  ValueError: in case of parsing errors.
                """

                if not byte_string(der_encoded):
                    raise ValueError("Input is not a byte string")

                s = BytesIO_EOF(der_encoded)
                self._decodeFromStream(s, strict)

                # There shouldn't be other bytes left
                if s.remaining_data() > 0:
                    raise ValueError("Unexpected extra data after the DER structure")

                return self

        def _decodeFromStream(self, s, strict):
                """Decode a complete DER element from a file."""

                idOctet = s.read_byte()
                if self._tag_octet is not None:
                    if idOctet != self._tag_octet:
                        raise ValueError("Unexpected DER tag")
                else:
                    self._tag_octet = idOctet
                length = self._decodeLen(s)
                self.payload = s.read(length)

                # In case of an EXTERNAL tag, further decode the inner
                # element.
                if hasattr(self, "_inner_tag_octet"):
                    p = BytesIO_EOF(self.payload)
                    inner_octet = p.read_byte()
                    if inner_octet != self._inner_tag_octet:
                        raise ValueError("Unexpected internal DER tag")
                    length = self._decodeLen(p)
                    self.payload = p.read(length)

                    # There shouldn't be other bytes left
                    if p.remaining_data() > 0:
                        raise ValueError("Unexpected extra data after the DER structure")


class DerInteger(DerObject):
        """Class to model a DER INTEGER.

        An example of encoding is::

          >>> from Crypto.Util.asn1 import DerInteger
          >>> from binascii import hexlify, unhexlify
          >>> int_der = DerInteger(9)
          >>> print hexlify(int_der.encode())

        which will show ``020109``, the DER encoding of 9.

        And for decoding::

          >>> s = unhexlify(b'020109')
          >>> try:
          >>>   int_der = DerInteger()
          >>>   int_der.decode(s)
          >>>   print int_der.value
          >>> except ValueError:
          >>>   print "Not a valid DER INTEGER"

        the output will be ``9``.

        :ivar value: The integer value
        :vartype value: integer
        """

        def __init__(self, value=0, implicit=None, explicit=None):
                """Initialize the DER object as an INTEGER.

                :Parameters:
                  value : integer
                    The value of the integer.

                  implicit : integer
                    The IMPLICIT tag to use for the encoded object.
                    It overrides the universal tag for INTEGER (2).
                """

                DerObject.__init__(self, 0x02, b'', implicit,
                                   False, explicit)
                self.value = value  # The integer value

        def encode(self):
                """Return the DER INTEGER, fully encoded as a
                binary string."""

                number = self.value
                self.payload = b''
                while True:
                    self.payload = bchr(int(number & 255)) + self.payload
                    if 128 <= number <= 255:
                        self.payload = bchr(0x00) + self.payload
                    if -128 <= number <= 255:
                        break
                    number >>= 8
                return DerObject.encode(self)

        def decode(self, der_encoded, strict=False):
                """Decode a complete DER INTEGER DER, and re-initializes this
                object with it.

                Args:
                  der_encoded (byte string): A complete INTEGER DER element.

                Raises:
                  ValueError: in case of parsing errors.
                """

                return DerObject.decode(self, der_encoded, strict=strict)

        def _decodeFromStream(self, s, strict):
                """Decode a complete DER INTEGER from a file."""

                # Fill up self.payload
                DerObject._decodeFromStream(self, s, strict)

                if strict:
                    if len(self.payload) == 0:
                        raise ValueError("Invalid encoding for DER INTEGER: empty payload")
                    if len(self.payload) >= 2 and struct.unpack('>H', self.payload[:2])[0] < 0x80:
                        raise ValueError("Invalid encoding for DER INTEGER: leading zero")

                # Derive self.value from self.payload
                self.value = 0
                bits = 1
                for i in self.payload:
                    self.value *= 256
                    self.value += bord(i)
                    bits <<= 8
                if self.payload and bord(self.payload[0]) & 0x80:
                    self.value -= bits


class DerSequence(DerObject):
        """Class to model a DER SEQUENCE.

        This object behaves like a dynamic Python sequence.
Loading ...