Projekt

Obecné

Profil

Stáhnout (5.06 KB) Statistiky
| Větev: | Tag: | Revize:
1
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
    def _run_for_output(args=None, stdin=None, executable=OPENSSL_EXECUTABLE):
16
        """
17
        Launches a new process in which the given executable is run. STDIN and process arguments can be set.
18
        If the process ends with a non-zero then <CryptographyException> is raised.
19
        :param args: Arguments to be passed to the program.
20
        :param stdin: String input to be passed to the stdin of the created process.
21
        :param executable: Executable to be run (defaults to openssl)
22
        :return: If the process ends with a zero return code then the STDOUT of the process is returned as a byte array.
23
        """
24
        if args is None:
25
            args = []
26
        try:
27
            # prepend the name of the executable
28
            args.insert(0, executable)
29

    
30
            # create a new process
31
            proc = subprocess.Popen(args, stdin=subprocess.PIPE if stdin is not None else None, stdout=subprocess.PIPE,
32
                                    stderr=subprocess.PIPE)
33

    
34
            out, err = proc.communicate(stdin)
35

    
36
            if proc.returncode != 0:
37
                # if the process did not result in zero result code, then raise an exception
38
                if err is not None:
39
                    raise CryptographyException(executable, args, err.decode())
40
                else:
41
                    raise CryptographyException(executable, args,
42
                                                f""""Execution resulted in non-zero argument""")
43

    
44
            return out
45
        except FileNotFoundError:
46
            raise CryptographyException(executable, args, f""""{executable}" not found in the current PATH.""")
47

    
48
    def create_private_key(self, passphrase=None):
49
        """
50
        Creates a private key with the option to encrypt it using a passphrase.
51
        :param passphrase: A passphrase to be used when encrypting the key (if none is passed then the key is not
52
        encrypted at all). Empty passphrase ("") also results in a key that is not encrypted.
53
        :return: A text representation of the generated private key.
54
        """
55
        if passphrase is None or len(passphrase) == 0:
56
            return self._run_for_output(["genrsa", "2048"]).decode()
57
        else:
58
            return self._run_for_output(
59
                ["genrsa", PRIVATE_KEY_ENCRYPTION_METHOD, "-passout", f"pass:{passphrase}", "2048"]).decode()
60

    
61
    def create_sscrt(self, key, subject, config="", extensions="", key_passphrase=None):
62
        """
63
        Creates a root CA
64

    
65
        :param key: private key of the CA to be used
66
        :param subject: an instance of <Subject> representing the subject to be added to the certificate
67
        :param config: string containing the configuration to be used
68
        :param extensions: name of the section in the configuration representing extensions
69
        :param key_passphrase: passphrase of the private key
70

    
71
        :return: byte array containing the generated certificate
72
        """
73
        assert key is not None
74
        assert subject is not None
75

    
76
        subj_dict = {}
77
        if subject.common_name is not None:
78
            subj_dict["CN"] = subject.common_name
79
        if subject.country is not None:
80
            subj_dict["C"] = subject.country
81
        if subject.locality is not None:
82
            subj_dict["L"] = subject.locality
83
        if subject.state is not None:
84
            subj_dict["ST"] = subject.state
85
        if subject.organization is not None:
86
            subj_dict["O"] = subject.organization
87
        if subject.organization_unit is not None:
88
            subj_dict["OU"] = subject.organization_unit
89
        if subject.email_address is not None:
90
            subj_dict["emailAddress"] = subject.email_address
91

    
92
        # merge the subject into a "subj" parameter
93
        subj = "".join([f"/{key}={value}" for key, value in subj_dict.items()])
94

    
95
        with TemporaryFile("openssl.conf", config) as conf_path:
96
            args = ["req", "-x509", "-new", "-subj", subj,
97
                    "-key", "-"]
98
            if len(config) > 0:
99
                args.extend(["-config", conf_path])
100

    
101
            if len(extensions) > 0:
102
                args.extend(["-extensions", extensions])
103

    
104
            # it would be best to not send the pass phrase at all, but for some reason pytest then prompts for
105
            # the pass phrase (this does not happen when run from pycharm)
106

    
107
            # if key_passphrase is not None:
108
            args.extend(["-passin", f"pass:{key_passphrase}"])
109

    
110
            return self._run_for_output(args, stdin=bytes(key, encoding="utf-8")).decode()
111

    
112

    
113
class CryptographyException(Exception):
114

    
115
    def __init__(self, executable, args, message):
116
        self.executable = executable
117
        self.args = args
118
        self.message = message
119

    
120
    def __str__(self):
121
        return f"""
122
        EXECUTABLE: {self.executable}
123
        ARGS: {self.args}
124
        MESSAGE: {self.message}
125
        """
(2-2/2)