• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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