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 17"""Functions that load and write PEM-encoded files.""" 18 19import base64 20 21from rsa._compat import is_bytes, range 22 23 24def _markers(pem_marker): 25 """ 26 Returns the start and end PEM markers, as bytes. 27 """ 28 29 if not is_bytes(pem_marker): 30 pem_marker = pem_marker.encode('ascii') 31 32 return (b'-----BEGIN ' + pem_marker + b'-----', 33 b'-----END ' + pem_marker + b'-----') 34 35 36def load_pem(contents, pem_marker): 37 """Loads a PEM file. 38 39 :param contents: the contents of the file to interpret 40 :param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY' 41 when your file has '-----BEGIN RSA PRIVATE KEY-----' and 42 '-----END RSA PRIVATE KEY-----' markers. 43 44 :return: the base64-decoded content between the start and end markers. 45 46 @raise ValueError: when the content is invalid, for example when the start 47 marker cannot be found. 48 49 """ 50 51 # We want bytes, not text. If it's text, it can be converted to ASCII bytes. 52 if not is_bytes(contents): 53 contents = contents.encode('ascii') 54 55 (pem_start, pem_end) = _markers(pem_marker) 56 57 pem_lines = [] 58 in_pem_part = False 59 60 for line in contents.splitlines(): 61 line = line.strip() 62 63 # Skip empty lines 64 if not line: 65 continue 66 67 # Handle start marker 68 if line == pem_start: 69 if in_pem_part: 70 raise ValueError('Seen start marker "%s" twice' % pem_start) 71 72 in_pem_part = True 73 continue 74 75 # Skip stuff before first marker 76 if not in_pem_part: 77 continue 78 79 # Handle end marker 80 if in_pem_part and line == pem_end: 81 in_pem_part = False 82 break 83 84 # Load fields 85 if b':' in line: 86 continue 87 88 pem_lines.append(line) 89 90 # Do some sanity checks 91 if not pem_lines: 92 raise ValueError('No PEM start marker "%s" found' % pem_start) 93 94 if in_pem_part: 95 raise ValueError('No PEM end marker "%s" found' % pem_end) 96 97 # Base64-decode the contents 98 pem = b''.join(pem_lines) 99 return base64.standard_b64decode(pem) 100 101 102def save_pem(contents, pem_marker): 103 """Saves a PEM file. 104 105 :param contents: the contents to encode in PEM format 106 :param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY' 107 when your file has '-----BEGIN RSA PRIVATE KEY-----' and 108 '-----END RSA PRIVATE KEY-----' markers. 109 110 :return: the base64-encoded content between the start and end markers, as bytes. 111 112 """ 113 114 (pem_start, pem_end) = _markers(pem_marker) 115 116 b64 = base64.standard_b64encode(contents).replace(b'\n', b'') 117 pem_lines = [pem_start] 118 119 for block_start in range(0, len(b64), 64): 120 block = b64[block_start:block_start + 64] 121 pem_lines.append(block) 122 123 pem_lines.append(pem_end) 124 pem_lines.append(b'') 125 126 return b'\n'.join(pem_lines) 127