Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 6c098d6e

Přidáno uživatelem Stanislav Král před asi 4 roky(ů)

Re #8472 - Added make_csr method that makes a CSR

Added 2 unit tests testing the added method.
Moved CryptographyService tests into a separate files

Zobrazit rozdíly:

proj/services/cryptography.py
12 12
class CryptographyService:
13 13

  
14 14
    @staticmethod
15
    def _run_for_output(args=None, stdin=None, executable=OPENSSL_EXECUTABLE):
15
    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):
16 37
        """
17 38
        Launches a new process in which the given executable is run. STDIN and process arguments can be set.
18 39
        If the process ends with a non-zero then <CryptographyException> is raised.
19 40
        :param args: Arguments to be passed to the program.
20
        :param stdin: String input to be passed to the stdin of the created process.
41
        :param proc_input: String input to be passed to the stdin of the created process.
21 42
        :param executable: Executable to be run (defaults to openssl)
22 43
        :return: If the process ends with a zero return code then the STDOUT of the process is returned as a byte array.
23 44
        """
......
28 49
            args.insert(0, executable)
29 50

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

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

  
36 57
            if proc.returncode != 0:
37 58
                # if the process did not result in zero result code, then raise an exception
......
73 94
        assert key is not None
74 95
        assert subject is not None
75 96

  
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()])
97
        subj = self.subject_to_param_format(subject)
94 98

  
95 99
        with TemporaryFile("openssl.conf", config) as conf_path:
96 100
            args = ["req", "-x509", "-new", "-subj", subj,
......
107 111
            # if key_passphrase is not None:
108 112
            args.extend(["-passin", f"pass:{key_passphrase}"])
109 113

  
110
            return self._run_for_output(args, stdin=bytes(key, encoding="utf-8")).decode()
114
            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()
111 134

  
112 135

  
113 136
class CryptographyException(Exception):
proj/tests/services/cryptography/conftest.py
1
import pytest
2

  
3
from proj.services.cryptography import CryptographyService
4

  
5

  
6
@pytest.fixture
7
def service():
8
    # provide a CryptographyService fixture
9
    return CryptographyService()
proj/tests/services/cryptography/make_csr_test.py
1
import subprocess
2

  
3
from proj.model.subject import Subject
4

  
5

  
6
def get_csr_pem(csr):
7
    return subprocess.check_output(["openssl", "req", "-noout", "-text", "-verify", "-in", "-"],
8
                                   input=bytes(csr, encoding="utf-8"), stderr=subprocess.STDOUT).decode()
9

  
10

  
11
def test_make_csr(service):
12
    private_key = service.create_private_key()
13

  
14
    subject = Subject(common_name="foo", country="CZ")
15
    csr = service.make_csr(subject, private_key)
16

  
17
    assert "Subject: CN = foo, C = CZ" in get_csr_pem(csr)
18

  
19

  
20
def test_make_csr_pkey_passphrase(service):
21
    private_key = service.create_private_key(passphrase="foobar")
22

  
23
    subject = Subject(common_name="foo", country="CZ", organization_unit="Mysterious Unit")
24
    csr = service.make_csr(subject, private_key, subject_key_pass="foobar")
25

  
26
    assert "Subject: CN = foo, C = CZ, OU = Mysterious Unit" in get_csr_pem(csr)
proj/tests/services/cryptography/private_keys_test.py
1
import pytest
2
import subprocess
3

  
4

  
5
def test_private_key(service):
6
    private_key = service.create_private_key()
7

  
8
    # verify the private key
9
    subprocess.check_output(["openssl", "rsa", "-in", "-", "-check"], input=bytes(private_key, encoding="utf-8"),
10
                            stderr=subprocess.STDOUT)
11

  
12

  
13
def test_encrypted_private_key(service):
14
    private_key = service.create_private_key(passphrase="foobar")
15

  
16
    # verify the private key providing a correct passphrase
17
    subprocess.check_output(["openssl", "rsa", "-in", "-", "-passin", "pass:foobar", "-check"],
18
                            input=bytes(private_key, encoding="utf-8"), stderr=subprocess.STDOUT)
19

  
20

  
21
def test_encrypted_private_key_incorrect_pass(service):
22
    private_key = service.create_private_key(passphrase="foobar")
23

  
24
    # incorrect passphrase provided
25
    with pytest.raises(subprocess.CalledProcessError):
26
        subprocess.check_output(["openssl", "rsa", "-in", "-", "-passin", "pass:bazbaz", "-check"],
27
                                input=bytes(private_key, encoding="utf-8"), stderr=subprocess.STDOUT)
proj/tests/services/cryptography/self_signed_cert_test.py
1
import pytest
2
import subprocess
3

  
4
from proj.model.subject import Subject
5
from proj.services.cryptography import CryptographyService, CryptographyException
6

  
7

  
8
def test_create_sscrt(service):
9
    # create a self signed certificate using configuration and extensions
10
    private_key = service.create_private_key(passphrase="foobar")
11

  
12
    # distinguished_name is always required
13
    config = """
14
    # Simple Root CA
15

  
16
    [ req ]
17
    distinguished_name      = ca_dn                 # DN section
18

  
19
    [ ca_dn ]
20

  
21
    [ root_ca_ext ]
22
    keyUsage                = critical,keyCertSign,cRLSign
23
    basicConstraints        = critical,CA:true
24
    subjectKeyIdentifier    = hash
25
    authorityKeyIdentifier  = keyid:always
26
    """
27

  
28
    cert = service.create_sscrt(private_key,
29
                                Subject(common_name="Topnax",
30
                                        country="CZ",
31
                                        locality="My Locality",
32
                                        state="My state",
33
                                        organization="Mysterious Org.",
34
                                        organization_unit="Department of Mysteries",
35
                                        email_address="mysterious@box.cz"),
36
                                config=config,
37
                                extensions="root_ca_ext",
38
                                key_passphrase="foobar")
39

  
40
    cert_printed = subprocess.check_output(["openssl", "x509", "-noout", "-text", "-in", "-"],
41
                                           input=bytes(cert, encoding="utf-8"), stderr=subprocess.STDOUT).decode()
42

  
43
    assert "Certificate Sign, CRL Sign" in cert_printed
44
    assert "X509v3 Key Usage: critical" in cert_printed
45
    assert "CA:TRUE" in cert_printed
46

  
47
    assert "Issuer: CN = Topnax, C = CZ, L = My Locality, ST = My state, O = Mysterious Org., OU = Department of Mysteries, emailAddress = mysterious@box.cz" in cert_printed
48
    assert "Subject: CN = Topnax, C = CZ, L = My Locality, ST = My state, O = Mysterious Org., OU = Department of Mysteries, emailAddress = mysterious@box.cz" in cert_printed
49

  
50

  
51
def test_create_sscrt_config_without_extensions(service):
52
    # create a self signed certificate without specifying extensions
53
    private_key = service.create_private_key()
54

  
55
    config = """
56
    # Simple Root CA
57

  
58
    [ req ]
59
    distinguished_name      = ca_dn                 # DN section
60

  
61
    [ ca_dn ]
62

  
63
    """
64

  
65
    cert = service.create_sscrt(private_key, Subject(common_name="Topnax", country="CZ"), config=config)
66

  
67
    cert_printed = subprocess.check_output(["openssl", "x509", "-noout", "-text", "-in", "-"],
68
                                           input=bytes(cert, encoding="utf-8"), stderr=subprocess.STDOUT).decode()
69

  
70
    # TODO pass something in the configuration that can be asserted
71
    assert "Issuer: CN = Topnax, C = CZ" in cert_printed
72
    assert "Subject: CN = Topnax, C = CZ" in cert_printed
73

  
74

  
75
def test_create_sscrt_plain(service):
76
    # create a self signed certificate without configuration
77
    private_key = service.create_private_key()
78

  
79
    cert = service.create_sscrt(private_key, Subject(common_name="Topnax", country="CZ"))
80

  
81
    cert_printed = subprocess.check_output(["openssl", "x509", "-noout", "-text", "-in", "-"],
82
                                           input=bytes(cert, encoding="utf-8"), stderr=subprocess.STDOUT).decode()
83

  
84
    assert "Issuer: CN = Topnax, C = CZ" in cert_printed
85
    assert "Subject: CN = Topnax, C = CZ" in cert_printed
86

  
87

  
88
def test_create_sscrt_passphrase(service):
89
    # create a self signed certificate with a PK that is protected by a passphrase
90
    private_key = service.create_private_key(passphrase="foobar")
91

  
92
    cert = service.create_sscrt(private_key, Subject(common_name="Topnax", country="CZ"), key_passphrase="foobar")
93

  
94
    cert_printed = subprocess.check_output(["openssl", "x509", "-noout", "-text", "-in", "-"],
95
                                           input=bytes(cert, encoding="utf-8"), stderr=subprocess.STDOUT).decode()
96

  
97
    assert "Issuer: CN = Topnax, C = CZ" in cert_printed
98
    assert "Subject: CN = Topnax, C = CZ" in cert_printed
99

  
100

  
101
def test_create_sscrt_incorrect_passphrase(service):
102
    # make an attempt to create a self signed certificate using a private key with specifying wrong key passphrase or
103
    # no passphrase at all
104
    private_key = service.create_private_key(passphrase="foobar")
105

  
106
    # incorrect passphrase provided when using a protected private key
107
    with pytest.raises(CryptographyException) as e:
108
        service.create_sscrt(private_key, Subject(common_name="Topnax", country="CZ"), key_passphrase="bazfoo")
109
    assert "bad decrypt" in e.value.message
110

  
111
    # no passphrase provided when using a protected private key
112
    with pytest.raises(CryptographyException) as e:
113
        service.create_sscrt(private_key, Subject(common_name="Topnax", country="CZ"))
114
    assert "bad decrypt" in e.value.message
115

  
proj/tests/services/cryptography_test.py
1
import pytest
2
import subprocess
3

  
4
from proj.model.subject import Subject
5
from proj.services.cryptography import CryptographyService, CryptographyException
6

  
7

  
8
@pytest.fixture
9
def service():
10
    # provide a CryptographyService fixture
11
    return CryptographyService()
12

  
13

  
14
def test_private_key(service):
15
    private_key = service.create_private_key()
16

  
17
    # verify the private key
18
    subprocess.check_output(["openssl", "rsa", "-in", "-", "-check"], input=bytes(private_key, encoding="utf-8"),
19
                            stderr=subprocess.STDOUT)
20

  
21

  
22
def test_encrypted_private_key(service):
23
    private_key = service.create_private_key(passphrase="foobar")
24

  
25
    # verify the private key providing a correct passphrase
26
    subprocess.check_output(["openssl", "rsa", "-in", "-", "-passin", "pass:foobar", "-check"],
27
                            input=bytes(private_key, encoding="utf-8"), stderr=subprocess.STDOUT)
28

  
29

  
30
def test_encrypted_private_key_incorrect_pass(service):
31
    private_key = service.create_private_key(passphrase="foobar")
32

  
33
    # incorrect passphrase provided
34
    with pytest.raises(subprocess.CalledProcessError):
35
        subprocess.check_output(["openssl", "rsa", "-in", "-", "-passin", "pass:bazbaz", "-check"],
36
                                input=bytes(private_key, encoding="utf-8"), stderr=subprocess.STDOUT)
37

  
38

  
39
def test_create_sscrt(service):
40
    # create a self signed certificate using configuration and extensions
41
    private_key = service.create_private_key(passphrase="foobar")
42

  
43
    # distinguished_name is always required
44
    config = """
45
    # Simple Root CA
46

  
47
    [ req ]
48
    distinguished_name      = ca_dn                 # DN section
49

  
50
    [ ca_dn ]
51

  
52
    [ root_ca_ext ]
53
    keyUsage                = critical,keyCertSign,cRLSign
54
    basicConstraints        = critical,CA:true
55
    subjectKeyIdentifier    = hash
56
    authorityKeyIdentifier  = keyid:always
57
    """
58

  
59
    cert = service.create_sscrt(private_key,
60
                                Subject(common_name="Topnax",
61
                                        country="CZ",
62
                                        locality="My Locality",
63
                                        state="My state",
64
                                        organization="Mysterious Org.",
65
                                        organization_unit="Department of Mysteries",
66
                                        email_address="mysterious@box.cz"),
67
                                config=config,
68
                                extensions="root_ca_ext",
69
                                key_passphrase="foobar")
70

  
71
    cert_printed = subprocess.check_output(["openssl", "x509", "-noout", "-text", "-in", "-"],
72
                                           input=bytes(cert, encoding="utf-8"), stderr=subprocess.STDOUT).decode()
73

  
74
    assert "Certificate Sign, CRL Sign" in cert_printed
75
    assert "X509v3 Key Usage: critical" in cert_printed
76
    assert "CA:TRUE" in cert_printed
77

  
78
    assert "Issuer: CN = Topnax, C = CZ, L = My Locality, ST = My state, O = Mysterious Org., OU = Department of Mysteries, emailAddress = mysterious@box.cz" in cert_printed
79
    assert "Subject: CN = Topnax, C = CZ, L = My Locality, ST = My state, O = Mysterious Org., OU = Department of Mysteries, emailAddress = mysterious@box.cz" in cert_printed
80

  
81

  
82
def test_create_sscrt_config_without_extensions(service):
83
    # create a self signed certificate without specifying extensions
84
    private_key = service.create_private_key()
85

  
86
    config = """
87
    # Simple Root CA
88

  
89
    [ req ]
90
    distinguished_name      = ca_dn                 # DN section
91

  
92
    [ ca_dn ]
93

  
94
    """
95

  
96
    cert = service.create_sscrt(private_key, Subject(common_name="Topnax", country="CZ"), config=config)
97

  
98
    cert_printed = subprocess.check_output(["openssl", "x509", "-noout", "-text", "-in", "-"],
99
                                           input=bytes(cert, encoding="utf-8"), stderr=subprocess.STDOUT).decode()
100

  
101
    # TODO pass something in the configuration that can be asserted
102
    assert "Issuer: CN = Topnax, C = CZ" in cert_printed
103
    assert "Subject: CN = Topnax, C = CZ" in cert_printed
104

  
105

  
106
def test_create_sscrt_plain(service):
107
    # create a self signed certificate without configuration
108
    private_key = service.create_private_key()
109

  
110
    cert = service.create_sscrt(private_key, Subject(common_name="Topnax", country="CZ"))
111

  
112
    cert_printed = subprocess.check_output(["openssl", "x509", "-noout", "-text", "-in", "-"],
113
                                           input=bytes(cert, encoding="utf-8"), stderr=subprocess.STDOUT).decode()
114

  
115
    assert "Issuer: CN = Topnax, C = CZ" in cert_printed
116
    assert "Subject: CN = Topnax, C = CZ" in cert_printed
117

  
118

  
119
def test_create_sscrt_passphrase(service):
120
    # create a self signed certificate with a PK that is protected by a passphrase
121
    private_key = service.create_private_key(passphrase="foobar")
122

  
123
    cert = service.create_sscrt(private_key, Subject(common_name="Topnax", country="CZ"), key_passphrase="foobar")
124

  
125
    cert_printed = subprocess.check_output(["openssl", "x509", "-noout", "-text", "-in", "-"],
126
                                           input=bytes(cert, encoding="utf-8"), stderr=subprocess.STDOUT).decode()
127

  
128
    assert "Issuer: CN = Topnax, C = CZ" in cert_printed
129
    assert "Subject: CN = Topnax, C = CZ" in cert_printed
130

  
131

  
132
def test_create_sscrt_incorrect_passphrase(service):
133
    # make an attempt to create a self signed certificate using a private key with specifying wrong key passphrase or
134
    # no passphrase at all
135
    private_key = service.create_private_key(passphrase="foobar")
136

  
137
    # incorrect passphrase provided when using a protected private key
138
    with pytest.raises(CryptographyException) as e:
139
        service.create_sscrt(private_key, Subject(common_name="Topnax", country="CZ"), key_passphrase="bazfoo")
140
    assert "bad decrypt" in e.value.message
141

  
142
    # no passphrase provided when using a protected private key
143
    with pytest.raises(CryptographyException) as e:
144
        service.create_sscrt(private_key, Subject(common_name="Topnax", country="CZ"))
145
    assert "bad decrypt" in e.value.message

Také k dispozici: Unified diff