| # 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)] |