# -*- 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 ...