1# -*- coding: utf-8 -*-
2#
3# (c) 2019, Felix Fontein <felix@fontein.de>
4#
5# Ansible is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# Ansible is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
17
18from __future__ import absolute_import, division, print_function
19__metaclass__ = type
20
21
22PEM_START = '-----BEGIN '
23PEM_END = '-----'
24PKCS8_PRIVATEKEY_NAMES = ('PRIVATE KEY', 'ENCRYPTED PRIVATE KEY')
25PKCS1_PRIVATEKEY_SUFFIX = ' PRIVATE KEY'
26
27
28def identify_pem_format(content):
29    '''Given the contents of a binary file, tests whether this could be a PEM file.'''
30    try:
31        lines = content.decode('utf-8').splitlines(False)
32        if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END):
33            return True
34    except UnicodeDecodeError:
35        pass
36    return False
37
38
39def identify_private_key_format(content):
40    '''Given the contents of a private key file, identifies its format.'''
41    # See https://github.com/openssl/openssl/blob/master/crypto/pem/pem_pkey.c#L40-L85
42    # (PEM_read_bio_PrivateKey)
43    # and https://github.com/openssl/openssl/blob/master/include/openssl/pem.h#L46-L47
44    # (PEM_STRING_PKCS8, PEM_STRING_PKCS8INF)
45    try:
46        lines = content.decode('utf-8').splitlines(False)
47        if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END):
48            name = lines[0][len(PEM_START):-len(PEM_END)]
49            if name in PKCS8_PRIVATEKEY_NAMES:
50                return 'pkcs8'
51            if len(name) > len(PKCS1_PRIVATEKEY_SUFFIX) and name.endswith(PKCS1_PRIVATEKEY_SUFFIX):
52                return 'pkcs1'
53            return 'unknown-pem'
54    except UnicodeDecodeError:
55        pass
56    return 'raw'
57
58
59def split_pem_list(text, keep_inbetween=False):
60    '''
61    Split concatenated PEM objects into a list of strings, where each is one PEM object.
62    '''
63    result = []
64    current = [] if keep_inbetween else None
65    for line in text.splitlines(True):
66        if line.strip():
67            if not keep_inbetween and line.startswith('-----BEGIN '):
68                current = []
69            if current is not None:
70                current.append(line)
71                if line.startswith('-----END '):
72                    result.append(''.join(current))
73                    current = [] if keep_inbetween else None
74    return result
75