Projekt

Obecné

Profil

Stáhnout (10.3 KB) Statistiky
| Větev: | Tag: | Revize:
1 d28c02f7 Stanislav Král
import subprocess
2
import os
3
import sys
4
5
# directory where all certificates/keys will be stored
6
WORK_DIR = "static/openssl"
7
8
# name of the config file to be generated used when accepting a CSR
9
ACCEPT_CSR_CONF_FILE = "accept_csr.cnf"
10
11
# line to be inserted into the configuration file to be used when accepting a CA CSR
12
CA_CNF_LINE = "basicConstraints=critical,CA:TRUE"
13
14
15
# makes a private key without a passphrase (potentially insecure)
16
def make_private_key():
17
    return subprocess.check_output(["openssl", "genrsa", "2048"], stderr=subprocess.STDOUT)
18
19
20
def make_root_ca(name, key, days):
21
    """
22
    Creates a root CA
23
24
    :param name: name of the root CA to be used (will be passed to the Common Name field)
25
    :param key: private key of the CA to be used
26
    :param days: number of days for which the CA will be considered as valid
27
    :return: byte array containing the generated certificate
28
    """
29
    return subprocess.check_output(
30
        ["openssl", "req", "-new", "-x509", "-days", str(days), "-subj", f"/CN={name}", "-key", "-"],
31
        input=bytes(key, encoding="utf-8"), stderr=subprocess.STDOUT
32
    )
33
34
35
def make_csr(key, name):
36
    """
37
    Makes a CSR (Certificate Signing Request)
38
    :param key: the private key to be used to generate the certificate
39
    :param name: name of the certificate (will be passed to the Common Name field)
40
    :return: byte array containing the generated certificate signing request
41
    """
42
    return subprocess.check_output(
43
        ["openssl", "req", "-new", "-subj", f"/CN={name}", "-key", "-"],
44
        input=bytes(key, encoding="utf-8"), stderr=subprocess.STDOUT
45
    )
46
47
48
def sign_csr(csr, ca_cert, ca_key, days, config=""):
49
    """
50
    Signs the given CSR by the given CA
51
    :param csr: A string containing the CSR to be accepted
52
    :param ca_cert: A string containing the certificate of the issuer that will sign this CSR
53
    :param ca_key: A string containing the private key of the issuer's certificate
54
    :param days: The number of days for which the generated certificate will be considered as valid
55
    :param config: A string containing the configuration of extensions to be used
56
    :return: Byte array containing the generated and signed certificate
57
    """
58
59
    # check whether any config has been passed
60
    config_used = len(config) > 0
61
    config_file = get_path(ACCEPT_CSR_CONF_FILE)
62
    if config_used:
63
        # if config is to be applied the store it temporarily in a special file
64
        # that will be later passed to the openssl program
65
        puts(config, config_file)
66
67
    # prepare openssl parameters...
68
    # CSR, CA and CA's private key will be passed via stdin (that's the meaning of the '-' symbol)
69
    params = ["openssl", "x509", "-req", "-days", str(days), "-in", "-", "-CA", "-", "-CAkey", "-", "-CAcreateserial"]
70
71
    if config_used:
72
        # if config is to be applied add the generated configuration file path to the openssl program via parameters
73
        params.append("-extfile")
74
        params.append(config_file)
75
76
    # TODO delete any created files
77
    return subprocess.check_output(params, input=(bytes(csr + ca_cert + ca_key, encoding="utf-8")),
78
                                   stderr=subprocess.STDOUT)
79
80
81
def get_aia_cnf_line(uri):
82
    """
83
     Generates a line to be used in a configuration of X509 extensions that specifies that the current certificate will
84
     use Authority Information Access field to generate the chain of trust.
85
    :param uri: URI where the parent certificate lies
86
    :return:  A line to be used in a configuration of X509 extensions
87
    """
88
    return f"authorityInfoAccess = caIssuers;URI:{uri}"
89
90
91
# returns a path of a file in a work directory
92
def get_path(file):
93
    return f"{WORK_DIR}/{file}"
94
95
96
# copies the given string to the file specified by a path
97
def puts(content, file):
98
    with open(file, "w") as file:
99
        file.write(content)
100
101
102
# reads all content of a file specified by a path
103
def read_file(file):
104
    with open(file, "r") as file:
105
        return file.read()
106
107
108
def setup_root_intermediate_cas():
109
    """
110
    Generates a root and an intermediate certificate authorities
111
    """
112
113
    # generate a private key to be used to generate a root CA
114
    root_key = make_private_key().decode()
115
    # use the generated key to create a root CA
116
    root_ca = make_root_ca("ROOT CA", root_key, 1825).decode()
117
118
    # generate a private key to be used to generate an intermediate CA
119
    intermediate_key = make_private_key().decode()
120
    # use the generated key to make a CSR for a intermediate CA
121
    intermediate_csr = make_csr(intermediate_key, "INTER CA").decode()
122
    # sign the generated CSR by root CA
123
    # specify AIA field and that this certificate is a CA
124
    intermediate_ca = sign_csr(intermediate_csr, root_ca, root_key, 1096,
125
                               f"""
126
                    {get_aia_cnf_line("http://localhost:5000/static/openssl/root.crt")}
127
                    {CA_CNF_LINE}
128
                    """).decode()
129
130
    # store the generated root CA certificate/private key to files
131
    puts(root_ca, get_path("root.crt"))
132
    puts(root_key, get_path("root.key"))
133
134
    # store the generated intermediate CA certificate/private key to files
135
    puts(intermediate_ca, get_path("inter.crt"))
136
    puts(intermediate_key, get_path("inter.key"))
137
138
    # return the generated intermediate CA certificate
139
    return intermediate_ca, intermediate_key
140
141
142
# USAGE (create a chain of trust that consists of root and inter. CAs and a generated certificate signed by int. CA):
143
#
144
# "python3 openssl_poc.py MyTestCertificate"
145
#
146
# the generated certificate will have the Common Name field set to "MyTestCertificate"
147
# and will be stored to MyTestCertificate.crt
148
if __name__ == "__main__":
149
    # a name of the child certificate to be generated must be passed to the program via arguments
150
    if len(sys.argv) > 1:
151
        cert_name = sys.argv[1]
152
153
        # ensure that WORK_DIR directory exists
154
        try:
155
            os.mkdir(WORK_DIR)
156
        except IOError:
157
            pass
158
159
        # the program will generate a child certificate that is signed by an intermediate CA
160
161
        # root CA certificate, intermediate CA certificate and the generated user specified child certificate will be
162
        # save to static/openssl directory
163
164
        # if any of the files required to create such certificate is missing then the program will regenerate the whole
165
        # chain of trust (root/inter. CA cert/key)
166
167
        try:
168
            # try to read all required files (exception is thrown if any of the files are non-existent)
169
            read_file(get_path("root.crt"))
170
            inter_ca = read_file(get_path("inter.crt"))
171
            inter_key = read_file(get_path("inter.key"))
172
        except (IOError, FileNotFoundError):
173
            print("Creating ROOT and INTERMEDIATE CAs...")
174
            # any of the required files is missing - generate new ones
175
            (inter_ca, inter_key) = setup_root_intermediate_cas()
176
177
        # make a private key to be used for generating a child certificate
178
        child_key = make_private_key().decode()
179
180
        #  generate Common Name field to be specified in the generated child certificate
181
        child_cert_name = f"{cert_name} - CERT"
182
183
        # generate a CSR to sign the child certificate
184
        child_csr = make_csr(child_key, child_cert_name).decode()
185
186
        # sign the CSR by the intermediate CA (make sure inter. cert. is available via AIA)
187
        child_ca = sign_csr(child_csr, inter_ca, inter_key, 1096,
188
                            f"""
189
                                {get_aia_cnf_line("http://localhost:5000/static/openssl/inter.crt")}
190
                                """).decode()
191
192
        # generate a path of a file in which the child cert. will be stored
193
        child_ca_file = get_path(f"{cert_name}.crt")
194
        # store the generated child cert.
195
        puts(child_ca, child_ca_file)
196
197
        # store the generated private key used to generate the child. cert
198
        puts(child_key, get_path(f"{cert_name}.key"))
199
200
        #####################################
201
        # TEST SECTION
202
203
        # read content of the root CA certificate
204
        root_cert_content = subprocess.check_output(["openssl", "x509", "-noout", "-text", "-in", "-"],
205
                                                    input=bytes(read_file(get_path("root.crt")),
206
                                                                encoding="utf-8")).decode()
207
208
        # assert the common name of the certificate and that the certificate represents a CA
209
        assert f"Subject: CN = ROOT CA" in root_cert_content
210
        assert "CA:TRUE" in root_cert_content
211
212
        # read content of the intermediate CA certificate
213
        inter_cert_content = subprocess.check_output(["openssl", "x509", "-noout", "-text", "-in", "-"],
214
                                                     input=bytes(inter_ca, encoding="utf-8")).decode()
215
216
        # assert that the issuer of the certificate is the root CA
217
        assert "Issuer: CN = ROOT CA" in inter_cert_content
218
        # assert the common name
219
        assert f"Subject: CN = INTER CA" in inter_cert_content
220
        # assert that the certificate contains a reference to the root CA's certificate via AIA
221
        assert "CA Issuers - URI:http://localhost:5000/static/openssl/root.crt" in inter_cert_content
222
        # ensure that the generated certificate is indeed a CA
223
        assert "CA:TRUE" in inter_cert_content
224
225
        # read content of the child certificate
226
        child_cert_content = subprocess.check_output(
227
            ["openssl", "x509", "-noout", "-text", "-in", child_ca_file]).decode()
228
229
        # assert that the issuer is the intermediate CA
230
        assert "Issuer: CN = INTER CA" in child_cert_content
231
        # assert that the common name is the one passed via arguments
232
        assert f"Subject: CN = {child_cert_name}" in child_cert_content
233
        # assert that the certificate contains a reference to the intermediate CA's certificate via AIA
234
        assert "CA Issuers - URI:http://localhost:5000/static/openssl/inter.crt" in child_cert_content
235
        # assert that the generated certificate is not a CA
236
        assert "CA:TRUE" not in child_cert_content
237
238
        # assert that the chain of trust is valid (root -> intermediate -> child)
239
        assert subprocess.check_output(
240
            ["openssl", "verify", "-CAfile", get_path("root.crt"), "-untrusted", get_path("inter.crt"), child_ca_file])
241
242
        print(f"TESTS PASSED!!! \n{cert_name} certificate created successfully")