|
|
- # coding: utf-8
-
- """
- Encoding DER to PEM and decoding PEM to DER. Exports the following items:
-
- - armor()
- - detect()
- - unarmor()
-
- """
-
- from __future__ import unicode_literals, division, absolute_import, print_function
-
- import base64
- import re
- import sys
-
- from ._errors import unwrap
- from ._types import type_name as _type_name, str_cls, byte_cls
-
- if sys.version_info < (3,):
- from cStringIO import StringIO as BytesIO
- else:
- from io import BytesIO
-
-
- def detect(byte_string):
- """
- Detect if a byte string seems to contain a PEM-encoded block
-
- :param byte_string:
- A byte string to look through
-
- :return:
- A boolean, indicating if a PEM-encoded block is contained in the byte
- string
- """
-
- if not isinstance(byte_string, byte_cls):
- raise TypeError(unwrap(
- '''
- byte_string must be a byte string, not %s
- ''',
- _type_name(byte_string)
- ))
-
- return byte_string.find(b'-----BEGIN') != -1 or byte_string.find(b'---- BEGIN') != -1
-
-
- def armor(type_name, der_bytes, headers=None):
- """
- Armors a DER-encoded byte string in PEM
-
- :param type_name:
- A unicode string that will be capitalized and placed in the header
- and footer of the block. E.g. "CERTIFICATE", "PRIVATE KEY", etc. This
- will appear as "-----BEGIN CERTIFICATE-----" and
- "-----END CERTIFICATE-----".
-
- :param der_bytes:
- A byte string to be armored
-
- :param headers:
- An OrderedDict of the header lines to write after the BEGIN line
-
- :return:
- A byte string of the PEM block
- """
-
- if not isinstance(der_bytes, byte_cls):
- raise TypeError(unwrap(
- '''
- der_bytes must be a byte string, not %s
- ''' % _type_name(der_bytes)
- ))
-
- if not isinstance(type_name, str_cls):
- raise TypeError(unwrap(
- '''
- type_name must be a unicode string, not %s
- ''',
- _type_name(type_name)
- ))
-
- type_name = type_name.upper().encode('ascii')
-
- output = BytesIO()
- output.write(b'-----BEGIN ')
- output.write(type_name)
- output.write(b'-----\n')
- if headers:
- for key in headers:
- output.write(key.encode('ascii'))
- output.write(b': ')
- output.write(headers[key].encode('ascii'))
- output.write(b'\n')
- output.write(b'\n')
- b64_bytes = base64.b64encode(der_bytes)
- b64_len = len(b64_bytes)
- i = 0
- while i < b64_len:
- output.write(b64_bytes[i:i + 64])
- output.write(b'\n')
- i += 64
- output.write(b'-----END ')
- output.write(type_name)
- output.write(b'-----\n')
-
- return output.getvalue()
-
-
- def _unarmor(pem_bytes):
- """
- Convert a PEM-encoded byte string into one or more DER-encoded byte strings
-
- :param pem_bytes:
- A byte string of the PEM-encoded data
-
- :raises:
- ValueError - when the pem_bytes do not appear to be PEM-encoded bytes
-
- :return:
- A generator of 3-element tuples in the format: (object_type, headers,
- der_bytes). The object_type is a unicode string of what is between
- "-----BEGIN " and "-----". Examples include: "CERTIFICATE",
- "PUBLIC KEY", "PRIVATE KEY". The headers is a dict containing any lines
- in the form "Name: Value" that are right after the begin line.
- """
-
- if not isinstance(pem_bytes, byte_cls):
- raise TypeError(unwrap(
- '''
- pem_bytes must be a byte string, not %s
- ''',
- _type_name(pem_bytes)
- ))
-
- # Valid states include: "trash", "headers", "body"
- state = 'trash'
- headers = {}
- base64_data = b''
- object_type = None
-
- found_start = False
- found_end = False
-
- for line in pem_bytes.splitlines(False):
- if line == b'':
- continue
-
- if state == "trash":
- # Look for a starting line since some CA cert bundle show the cert
- # into in a parsed format above each PEM block
- type_name_match = re.match(b'^(?:---- |-----)BEGIN ([A-Z0-9 ]+)(?: ----|-----)', line)
- if not type_name_match:
- continue
- object_type = type_name_match.group(1).decode('ascii')
-
- found_start = True
- state = 'headers'
- continue
-
- if state == 'headers':
- if line.find(b':') == -1:
- state = 'body'
- else:
- decoded_line = line.decode('ascii')
- name, value = decoded_line.split(':', 1)
- headers[name] = value.strip()
- continue
-
- if state == 'body':
- if line[0:5] in (b'-----', b'---- '):
- der_bytes = base64.b64decode(base64_data)
-
- yield (object_type, headers, der_bytes)
-
- state = 'trash'
- headers = {}
- base64_data = b''
- object_type = None
- found_end = True
- continue
-
- base64_data += line
-
- if not found_start or not found_end:
- raise ValueError(unwrap(
- '''
- pem_bytes does not appear to contain PEM-encoded data - no
- BEGIN/END combination found
- '''
- ))
-
-
- def unarmor(pem_bytes, multiple=False):
- """
- Convert a PEM-encoded byte string into a DER-encoded byte string
-
- :param pem_bytes:
- A byte string of the PEM-encoded data
-
- :param multiple:
- If True, function will return a generator
-
- :raises:
- ValueError - when the pem_bytes do not appear to be PEM-encoded bytes
-
- :return:
- A 3-element tuple (object_name, headers, der_bytes). The object_name is
- a unicode string of what is between "-----BEGIN " and "-----". Examples
- include: "CERTIFICATE", "PUBLIC KEY", "PRIVATE KEY". The headers is a
- dict containing any lines in the form "Name: Value" that are right
- after the begin line.
- """
-
- generator = _unarmor(pem_bytes)
-
- if not multiple:
- return next(generator)
-
- return generator
|