選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 

135 行
3.9 KiB

  1. # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # https://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Functions that load and write PEM-encoded files."""
  15. import base64
  16. import typing
  17. # Should either be ASCII strings or bytes.
  18. FlexiText = typing.Union[str, bytes]
  19. def _markers(pem_marker: FlexiText) -> typing.Tuple[bytes, bytes]:
  20. """
  21. Returns the start and end PEM markers, as bytes.
  22. """
  23. if not isinstance(pem_marker, bytes):
  24. pem_marker = pem_marker.encode("ascii")
  25. return (
  26. b"-----BEGIN " + pem_marker + b"-----",
  27. b"-----END " + pem_marker + b"-----",
  28. )
  29. def _pem_lines(contents: bytes, pem_start: bytes, pem_end: bytes) -> typing.Iterator[bytes]:
  30. """Generator over PEM lines between pem_start and pem_end."""
  31. in_pem_part = False
  32. seen_pem_start = False
  33. for line in contents.splitlines():
  34. line = line.strip()
  35. # Skip empty lines
  36. if not line:
  37. continue
  38. # Handle start marker
  39. if line == pem_start:
  40. if in_pem_part:
  41. raise ValueError('Seen start marker "%r" twice' % pem_start)
  42. in_pem_part = True
  43. seen_pem_start = True
  44. continue
  45. # Skip stuff before first marker
  46. if not in_pem_part:
  47. continue
  48. # Handle end marker
  49. if in_pem_part and line == pem_end:
  50. in_pem_part = False
  51. break
  52. # Load fields
  53. if b":" in line:
  54. continue
  55. yield line
  56. # Do some sanity checks
  57. if not seen_pem_start:
  58. raise ValueError('No PEM start marker "%r" found' % pem_start)
  59. if in_pem_part:
  60. raise ValueError('No PEM end marker "%r" found' % pem_end)
  61. def load_pem(contents: FlexiText, pem_marker: FlexiText) -> bytes:
  62. """Loads a PEM file.
  63. :param contents: the contents of the file to interpret
  64. :param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
  65. when your file has '-----BEGIN RSA PRIVATE KEY-----' and
  66. '-----END RSA PRIVATE KEY-----' markers.
  67. :return: the base64-decoded content between the start and end markers.
  68. @raise ValueError: when the content is invalid, for example when the start
  69. marker cannot be found.
  70. """
  71. # We want bytes, not text. If it's text, it can be converted to ASCII bytes.
  72. if not isinstance(contents, bytes):
  73. contents = contents.encode("ascii")
  74. (pem_start, pem_end) = _markers(pem_marker)
  75. pem_lines = [line for line in _pem_lines(contents, pem_start, pem_end)]
  76. # Base64-decode the contents
  77. pem = b"".join(pem_lines)
  78. return base64.standard_b64decode(pem)
  79. def save_pem(contents: bytes, pem_marker: FlexiText) -> bytes:
  80. """Saves a PEM file.
  81. :param contents: the contents to encode in PEM format
  82. :param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
  83. when your file has '-----BEGIN RSA PRIVATE KEY-----' and
  84. '-----END RSA PRIVATE KEY-----' markers.
  85. :return: the base64-encoded content between the start and end markers, as bytes.
  86. """
  87. (pem_start, pem_end) = _markers(pem_marker)
  88. b64 = base64.standard_b64encode(contents).replace(b"\n", b"")
  89. pem_lines = [pem_start]
  90. for block_start in range(0, len(b64), 64):
  91. block = b64[block_start : block_start + 64]
  92. pem_lines.append(block)
  93. pem_lines.append(pem_end)
  94. pem_lines.append(b"")
  95. return b"\n".join(pem_lines)