pem.py 3.5 KB

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