Projekt

Obecné

Profil

Stáhnout (6 KB) Statistiky
| Větev: | Tag: | Revize:
1 c0aed2f5 Stanislav Král
import subprocess
2
3
# encryption method to be used when generating private keys
4
from proj.utils.temporary_file import TemporaryFile
5
6
PRIVATE_KEY_ENCRYPTION_METHOD = "-aes256"
7
8
# openssl executable name
9
OPENSSL_EXECUTABLE = "openssl"
10
11
12
class CryptographyService:
13
14
    @staticmethod
15 6c098d6e Stanislav Král
    def subject_to_param_format(subject):
16
        subj_dict = {}
17
        if subject.common_name is not None:
18
            subj_dict["CN"] = subject.common_name
19
        if subject.country is not None:
20
            subj_dict["C"] = subject.country
21
        if subject.locality is not None:
22
            subj_dict["L"] = subject.locality
23
        if subject.state is not None:
24
            subj_dict["ST"] = subject.state
25
        if subject.organization is not None:
26
            subj_dict["O"] = subject.organization
27
        if subject.organization_unit is not None:
28
            subj_dict["OU"] = subject.organization_unit
29
        if subject.email_address is not None:
30
            subj_dict["emailAddress"] = subject.email_address
31
32
        # merge the subject into a "subj" parameter format
33
        return "".join([f"/{key}={value}" for key, value in subj_dict.items()])
34
35
    @staticmethod
36
    def _run_for_output(args=None, proc_input=None, executable=OPENSSL_EXECUTABLE):
37 c0aed2f5 Stanislav Král
        """
38
        Launches a new process in which the given executable is run. STDIN and process arguments can be set.
39
        If the process ends with a non-zero then <CryptographyException> is raised.
40
        :param args: Arguments to be passed to the program.
41 6c098d6e Stanislav Král
        :param proc_input: String input to be passed to the stdin of the created process.
42 c0aed2f5 Stanislav Král
        :param executable: Executable to be run (defaults to openssl)
43
        :return: If the process ends with a zero return code then the STDOUT of the process is returned as a byte array.
44
        """
45
        if args is None:
46
            args = []
47
        try:
48
            # prepend the name of the executable
49
            args.insert(0, executable)
50
51
            # create a new process
52 6c098d6e Stanislav Král
            proc = subprocess.Popen(args, stdin=subprocess.PIPE if proc_input is not None else None, stdout=subprocess.PIPE,
53 c0aed2f5 Stanislav Král
                                    stderr=subprocess.PIPE)
54
55 6c098d6e Stanislav Král
            out, err = proc.communicate(proc_input)
56 c0aed2f5 Stanislav Král
57
            if proc.returncode != 0:
58
                # if the process did not result in zero result code, then raise an exception
59
                if err is not None:
60
                    raise CryptographyException(executable, args, err.decode())
61
                else:
62
                    raise CryptographyException(executable, args,
63
                                                f""""Execution resulted in non-zero argument""")
64
65
            return out
66
        except FileNotFoundError:
67
            raise CryptographyException(executable, args, f""""{executable}" not found in the current PATH.""")
68
69
    def create_private_key(self, passphrase=None):
70
        """
71
        Creates a private key with the option to encrypt it using a passphrase.
72
        :param passphrase: A passphrase to be used when encrypting the key (if none is passed then the key is not
73
        encrypted at all). Empty passphrase ("") also results in a key that is not encrypted.
74
        :return: A text representation of the generated private key.
75
        """
76
        if passphrase is None or len(passphrase) == 0:
77
            return self._run_for_output(["genrsa", "2048"]).decode()
78
        else:
79
            return self._run_for_output(
80
                ["genrsa", PRIVATE_KEY_ENCRYPTION_METHOD, "-passout", f"pass:{passphrase}", "2048"]).decode()
81
82
    def create_sscrt(self, key, subject, config="", extensions="", key_passphrase=None):
83
        """
84
        Creates a root CA
85
86
        :param key: private key of the CA to be used
87
        :param subject: an instance of <Subject> representing the subject to be added to the certificate
88
        :param config: string containing the configuration to be used
89
        :param extensions: name of the section in the configuration representing extensions
90
        :param key_passphrase: passphrase of the private key
91
92
        :return: byte array containing the generated certificate
93
        """
94
        assert key is not None
95
        assert subject is not None
96
97 6c098d6e Stanislav Král
        subj = self.subject_to_param_format(subject)
98 c0aed2f5 Stanislav Král
99
        with TemporaryFile("openssl.conf", config) as conf_path:
100
            args = ["req", "-x509", "-new", "-subj", subj,
101
                    "-key", "-"]
102
            if len(config) > 0:
103
                args.extend(["-config", conf_path])
104
105
            if len(extensions) > 0:
106
                args.extend(["-extensions", extensions])
107
108
            # it would be best to not send the pass phrase at all, but for some reason pytest then prompts for
109
            # the pass phrase (this does not happen when run from pycharm)
110
111
            # if key_passphrase is not None:
112
            args.extend(["-passin", f"pass:{key_passphrase}"])
113
114 6c098d6e Stanislav Král
            return self._run_for_output(args, proc_input=bytes(key, encoding="utf-8")).decode()
115
116
    def make_csr(self, subject, subject_key, subject_key_pass=None):
117
        """
118
        Makes a CSR (Certificate Signing Request)
119
120
        :param subject: an instance of <Subject> representing the subject to be added to the CSR
121
        :param subject_key: the private key of the subject to be used to generate the CSR
122
        :param subject_key_pass: passphrase of the subject's private key
123
        :return: byte array containing the generated certificate signing request
124
        """
125
126
        subj_param = self.subject_to_param_format(subject)
127
128
        args = ["req", "-new", "-subj", subj_param, "-key", "-"]
129
130
        if subject_key_pass is not None:
131
            args.extend(["-passin", f"pass:{subject_key_pass}"])
132
133
        return self._run_for_output(args, proc_input=bytes(subject_key, encoding="utf-8")).decode()
134 c0aed2f5 Stanislav Král
135
136
class CryptographyException(Exception):
137
138
    def __init__(self, executable, args, message):
139
        self.executable = executable
140
        self.args = args
141
        self.message = message
142
143
    def __str__(self):
144
        return f"""
145
        EXECUTABLE: {self.executable}
146
        ARGS: {self.args}
147
        MESSAGE: {self.message}
148
        """