1--TEST--
2openssl_error_string() tests (OpenSSL < 3.0)
3--EXTENSIONS--
4openssl
5--SKIPIF--
6<?php
7if (OPENSSL_VERSION_NUMBER >= 0x30000000) die('skip For OpenSSL < 3.0');
8?>
9--FILE--
10<?php
11// helper function to check openssl errors
12function expect_openssl_errors($name, $expected_error_codes) {
13    $expected_errors = array_fill_keys($expected_error_codes, false);
14    $all_errors = array();
15    while (($error_string = openssl_error_string()) !== false) {
16    if (preg_match(",.+:([0-9A-F]+):.+,", $error_string, $m) > 0) {
17            $error_code = $m[1];
18            if (isset($expected_errors[$error_code])) {
19                $expected_errors[$error_code] = true;
20            }
21        $all_errors[$error_code] = $error_string;
22        } else {
23        $all_errors[] = $error_string;
24    }
25    }
26
27    $fail = false;
28    foreach ($expected_errors as $error_code => $error_code_found) {
29        if (!$error_code_found) {
30            $fail = true;
31            echo "$name: no error code $error_code\n";
32        }
33    }
34
35    if (!$fail) {
36        echo "$name: ok\n";
37    } else {
38    echo "$name: uncaught errors\n";
39    foreach ($all_errors as $code => $str) {
40        if (!isset($expected_errors[$code]) || !$expected_errors[$code]) {
41            echo "\t", $code, ": ", $str, "\n";
42        }
43    }
44    }
45}
46
47// helper for debugging errors
48function dump_openssl_errors($name) {
49    echo "\n$name\n";
50    while (($error_string = openssl_error_string()) !== false) {
51        var_dump($error_string);
52    }
53}
54
55// common output file
56$output_file =  __DIR__ . "/openssl_error_string_basic_output.tmp";
57// invalid file for read is something that does not exist in current directory
58$invalid_file_for_read = __DIR__ . "/invalid_file_for_read_operation.txt";
59// invalid file for is the test dir as writing file to existing dir should always fail
60$invalid_file_for_write = __DIR__;
61// crt file
62$crt_file = "file://" . __DIR__ . "/cert.crt";
63// csr file
64$csr_file = "file://" . __DIR__ . "/cert.csr";
65// public key file
66$public_key_file = "file://" .__DIR__ . "/public.key";
67// private key file
68$private_key_file = "file://" .__DIR__ . "/private_rsa_1024.key";
69// private key file with password (password is 'php')
70$private_key_file_with_pass = "file://" .__DIR__ . "/private_rsa_2048_pass_php.key";
71
72// ENCRYPTION
73$data = "test";
74$method = "AES-128-ECB";
75$enc_key = str_repeat('x', 40);
76// error because password is longer then key length and
77// EVP_CIPHER_CTX_set_key_length fails for AES
78openssl_encrypt($data, $method, $enc_key);
79$enc_error = openssl_error_string();
80var_dump($enc_error);
81// make sure that error is cleared now
82var_dump(openssl_error_string());
83// internally OpenSSL ERR won't save more than 15 (16 - 1) errors so lets test it
84for ($i = 0; $i < 20; $i++) {
85    openssl_encrypt($data, $method, $enc_key);
86}
87$error_queue_size = 0;
88while (($enc_error_new = openssl_error_string()) !== false) {
89    if ($enc_error_new !== $enc_error) {
90        echo "The new encoding error doesn't match the expected one\n";
91    }
92    ++$error_queue_size;
93}
94var_dump($error_queue_size);
95echo "\n";
96
97$is_111 = OPENSSL_VERSION_NUMBER >= 0x10101000;
98$err_pem_no_start_line = $is_111 ? '0909006C': '0906D06C';
99
100// PKEY
101echo "PKEY errors\n";
102// file for pkey (file:///) fails when opennig (BIO_new_file)
103@openssl_pkey_export_to_file("file://" . $invalid_file_for_read, $output_file);
104expect_openssl_errors('openssl_pkey_export_to_file opening', ['02001002', '2006D080']);
105// file or private pkey is not correct PEM - failing PEM_read_bio_PrivateKey
106@openssl_pkey_export_to_file($csr_file, $output_file);
107expect_openssl_errors('openssl_pkey_export_to_file pem', [$err_pem_no_start_line]);
108// file to export cannot be written
109@openssl_pkey_export_to_file($private_key_file, $invalid_file_for_write);
110expect_openssl_errors('openssl_pkey_export_to_file write', ['2006D002']);
111// successful export
112@openssl_pkey_export($private_key_file_with_pass, $out, 'wrong pwd');
113expect_openssl_errors('openssl_pkey_export', ['06065064', '0906A065']);
114// invalid x509 for getting public key
115@openssl_pkey_get_public($private_key_file);
116expect_openssl_errors('openssl_pkey_get_public', [$err_pem_no_start_line]);
117// private encrypt with unknown padding
118@openssl_private_encrypt("data", $crypted, $private_key_file, 1000);
119expect_openssl_errors('openssl_private_encrypt', ['0408F090']);
120// private decrypt with failed padding check
121@openssl_private_decrypt("data", $crypted, $private_key_file, OPENSSL_PKCS1_OAEP_PADDING);
122expect_openssl_errors('openssl_private_decrypt', ['04099079']);
123// public encrypt and decrypt with failed padding check and padding
124@openssl_public_encrypt("data", $crypted, $public_key_file, 1000);
125@openssl_public_decrypt("data", $crypted, $public_key_file, OPENSSL_PKCS1_OAEP_PADDING);
126expect_openssl_errors('openssl_private_(en|de)crypt padding', [$err_pem_no_start_line, '0408F090', '06089093']);
127
128// X509
129echo "X509 errors\n";
130// file for x509 (file:///) fails when opennig (BIO_new_file)
131@openssl_x509_export_to_file("file://" . $invalid_file_for_read, $output_file);
132expect_openssl_errors('openssl_x509_export_to_file open', ['02001002']);
133// file or str cert is not correct PEM - failing PEM_read_bio_X509 or PEM_ASN1_read_bio
134@openssl_x509_export_to_file($csr_file, $output_file);
135expect_openssl_errors('openssl_x509_export_to_file pem', [$err_pem_no_start_line]);
136// file to export cannot be written
137@openssl_x509_export_to_file($crt_file, $invalid_file_for_write);
138expect_openssl_errors('openssl_x509_export_to_file write', ['2006D002']);
139// checking purpose fails because there is no such purpose 1000
140@openssl_x509_checkpurpose($crt_file, 1000);
141expect_openssl_errors('openssl_x509_checkpurpose purpose', ['0B086079']);
142
143// CSR
144echo "CSR errors\n";
145// file for csr (file:///) fails when opennig (BIO_new_file)
146@openssl_csr_get_subject("file://" . $invalid_file_for_read);
147expect_openssl_errors('openssl_csr_get_subject open', ['02001002', '2006D080']);
148// file or str csr is not correct PEM - failing PEM_read_bio_X509_REQ
149@openssl_csr_get_subject($crt_file);
150expect_openssl_errors('openssl_csr_get_subjec pem', [$err_pem_no_start_line]);
151
152// other possible causes that are difficult to catch:
153// - ASN1_STRING_to_UTF8 fails in add_assoc_name_entry
154// - invalid php_x509_request field (NULL) would cause error with CONF_get_string
155
156?>
157--CLEAN--
158<?php
159$output_file =  __DIR__ . "/openssl_error_string_basic_output.tmp";
160if (is_file($output_file)) {
161    unlink($output_file);
162}
163?>
164--EXPECT--
165string(89) "error:0607A082:digital envelope routines:EVP_CIPHER_CTX_set_key_length:invalid key length"
166bool(false)
167int(15)
168
169PKEY errors
170openssl_pkey_export_to_file opening: ok
171openssl_pkey_export_to_file pem: ok
172openssl_pkey_export_to_file write: ok
173openssl_pkey_export: ok
174openssl_pkey_get_public: ok
175openssl_private_encrypt: ok
176openssl_private_decrypt: ok
177openssl_private_(en|de)crypt padding: ok
178X509 errors
179openssl_x509_export_to_file open: ok
180openssl_x509_export_to_file pem: ok
181openssl_x509_export_to_file write: ok
182openssl_x509_checkpurpose purpose: ok
183CSR errors
184openssl_csr_get_subject open: ok
185openssl_csr_get_subjec pem: ok
186