Projekt

Obecné

Profil

Stáhnout (11 KB) Statistiky
| Větev: | Tag: | Revize:
1
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
# PFX store containing the whole chain of trust will also be generated (can be imported to Adobe Acrobat Reader)
149
if __name__ == "__main__":
150
    # a name of the child certificate to be generated must be passed to the program via arguments
151
    if len(sys.argv) > 1:
152
        cert_name = sys.argv[1]
153

    
154
        # ensure that WORK_DIR directory exists
155
        try:
156
            os.mkdir(WORK_DIR)
157
        except IOError:
158
            pass
159

    
160
        # the program will generate a child certificate that is signed by an intermediate CA
161

    
162
        # root CA certificate, intermediate CA certificate and the generated user specified child certificate will be
163
        # save to static/openssl directory
164

    
165
        # if any of the files required to create such certificate is missing then the program will regenerate the whole
166
        # chain of trust (root/inter. CA cert/key)
167

    
168
        try:
169
            # try to read all required files (exception is thrown if any of the files are non-existent)
170
            read_file(get_path("root.crt"))
171
            inter_ca = read_file(get_path("inter.crt"))
172
            inter_key = read_file(get_path("inter.key"))
173
        except (IOError, FileNotFoundError):
174
            print("Creating ROOT and INTERMEDIATE CAs...")
175
            # any of the required files is missing - generate new ones
176
            (inter_ca, inter_key) = setup_root_intermediate_cas()
177

    
178
        # make a private key to be used for generating a child certificate
179
        child_key = make_private_key().decode()
180

    
181
        #  generate Common Name field to be specified in the generated child certificate
182
        child_cert_name = f"{cert_name} - CERT"
183

    
184
        # generate a CSR to sign the child certificate
185
        child_csr = make_csr(child_key, child_cert_name).decode()
186

    
187
        # sign the CSR by the intermediate CA (make sure inter. cert. is available via AIA)
188
        child_ca = sign_csr(child_csr, inter_ca, inter_key, 1096,
189
                            f"""
190
                                {get_aia_cnf_line("http://localhost:5000/static/openssl/inter.crt")}
191
                                """).decode()
192

    
193
        # generate a path of a file in which the child cert. will be stored
194
        child_ca_file = get_path(f"{cert_name}.crt")
195
        # store the generated child cert.
196
        puts(child_ca, child_ca_file)
197

    
198
        # store the generated private key used to generate the child. cert
199
        child_key_file = get_path(f"{cert_name}.key")
200
        puts(child_key, child_key_file)
201

    
202
        # export the whole chain of trust including the generated child certificate to a PKCS store
203
        # "pass" passphrase will be used to encrypt the store
204
        subprocess.check_output(
205
            ["openssl", "pkcs12", "-export", "-out", f"{get_path(cert_name)}.pfx", "-inkey", child_key_file, "-in",
206
             child_ca_file, "-certfile", get_path("inter.crt"), "-passout", "pass:pass"],
207
            encoding="utf-8")
208

    
209
        #####################################
210
        # TEST SECTION
211

    
212
        # read content of the root CA certificate
213
        root_cert_content = subprocess.check_output(["openssl", "x509", "-noout", "-text", "-in", "-"],
214
                                                    input=bytes(read_file(get_path("root.crt")),
215
                                                                encoding="utf-8")).decode()
216

    
217
        # assert the common name of the certificate and that the certificate represents a CA
218
        assert f"Subject: CN = ROOT CA" in root_cert_content
219
        assert "CA:TRUE" in root_cert_content
220

    
221
        # read content of the intermediate CA certificate
222
        inter_cert_content = subprocess.check_output(["openssl", "x509", "-noout", "-text", "-in", "-"],
223
                                                     input=bytes(inter_ca, encoding="utf-8")).decode()
224

    
225
        # assert that the issuer of the certificate is the root CA
226
        assert "Issuer: CN = ROOT CA" in inter_cert_content
227
        # assert the common name
228
        assert f"Subject: CN = INTER CA" in inter_cert_content
229
        # assert that the certificate contains a reference to the root CA's certificate via AIA
230
        assert "CA Issuers - URI:http://localhost:5000/static/openssl/root.crt" in inter_cert_content
231
        # ensure that the generated certificate is indeed a CA
232
        assert "CA:TRUE" in inter_cert_content
233

    
234
        # read content of the child certificate
235
        child_cert_content = subprocess.check_output(
236
            ["openssl", "x509", "-noout", "-text", "-in", child_ca_file]).decode()
237

    
238
        # assert that the issuer is the intermediate CA
239
        assert "Issuer: CN = INTER CA" in child_cert_content
240
        # assert that the common name is the one passed via arguments
241
        assert f"Subject: CN = {child_cert_name}" in child_cert_content
242
        # assert that the certificate contains a reference to the intermediate CA's certificate via AIA
243
        assert "CA Issuers - URI:http://localhost:5000/static/openssl/inter.crt" in child_cert_content
244
        # assert that the generated certificate is not a CA
245
        assert "CA:TRUE" not in child_cert_content
246

    
247
        # assert that the chain of trust is valid (root -> intermediate -> child)
248
        assert subprocess.check_output(
249
            ["openssl", "verify", "-CAfile", get_path("root.crt"), "-untrusted", get_path("inter.crt"), child_ca_file])
250

    
251
        print(f"""TESTS PASSED!!! \n\n"{cert_name}" certificate created successfully""")
252
        print(
253
            f"""PFX store with the generated certificate and the whole chain of trust saved to:\n{cert_name}.pfx (protecteed by "pass" passphrase)""")
(7-7/9)