blob: 6848ccc537698f5ad027b300c0938448c8969546 [file] [log] [blame]
# Copyright (C) 2018 Sony Interactive Entertainment Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import re
def load(filesystem, path):
"""Load PEM file and return PEM object"""
return Pem(filesystem.read_text_file(path))
class BadFormatError(Exception):
"""Bad format error"""
pass
class Pem(object):
"""
Container for certificate related information.
Each section in PEM file can be accessible by get().
e.g.
pem = pemfile.load(filesystem, "/path/to/sample.pem")
assert pem.certificate.startswith("-----BEGIN CERTIFICATE-----")
"""
def __init__(self, content):
self._contents = _parse_pem_format(content)
if not self._contents:
raise BadFormatError("Cannot find any sections in this file.")
def get(self, kind):
"""Return requested information or None if not found."""
items = self.get_all(kind)
if not items:
raise KeyError("{} is not in this PEM".format(kind))
return items[0]
def get_all(self, kind):
"""Return all matching requested information"""
return [content for (key, content) in self._contents if key == kind]
@property
def certificate(self):
"""Return certificate"""
return self.get(CERTIFICATE)
@property
def private_key(self):
"""Return private key"""
return self.get(PRIVATE_KEY)
@property
def csr(self):
"""Return certificate request"""
return self.get(CERTIFICATE_REQUEST)
@property
def certificate_request(self):
"""Alias for csr()"""
return self.csr
@property
def certificate_signing_request(self):
"""Alias for csr()"""
return self.csr
MARKER = "-----"
BEGIN_MARKER = "BEGIN "
END_MARKER = "END"
CERTIFICATE_REQUEST = "CERTIFICATE REQUEST"
PRIVATE_KEY = "PRIVATE KEY"
CERTIFICATE = "CERTIFICATE"
BEGIN_PATTERN = re.compile("^{}BEGIN (.+){}$".format(MARKER, MARKER))
def _parse_pem_format(content):
lines = re.split('\r\n?|\n', content)
def find_begin(lines):
"""
Find first matching BEGIN marker.
@returns found key and rest of lines | None
"""
while lines:
matched = BEGIN_PATTERN.match(lines[0])
if matched:
return (matched.group(1), lines)
lines = lines[1:]
return None
def find_end(kind, lines):
"""
Find END marker.
@returns key, found contents and rest of lines.
@raise BadFormatError
"""
end_marker = "{}END {}{}".format(MARKER, kind, MARKER)
try:
index = lines.index(end_marker)
except ValueError:
raise BadFormatError("Cannot find section end: {}".format(end_marker))
return kind, "\n".join(lines[0:index + 1]) + "\n", lines[index + 1:]
def sections(lines):
"""Section Generator"""
while lines:
result = find_begin(lines)
if not result:
break
key, body, lines = find_end(*result)
yield (key, body)
return [section for section in sections(lines)]