[php-src] Issue #11054: openssl_get_publickey(): With public key PEM, results in error:0480006C:PEM routines::no start line

From: Date: Mon, 10 Apr 2023 16:04:44 +0000
Subject: [php-src] Issue #11054: openssl_get_publickey(): With public key PEM, results in error:0480006C:PEM routines::no start line
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-244168@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/11054 Author: famoser ### Description The following code: ```php <?php $publicKeyPem = <<EOD -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvYH14fT4DPgyffkDOrHt x0q+rxclB48h2ykgbR3QyDG2d7hMSXjtqEseO/iR1FdAv7UevIKyHFbHpJilOIwo mEqQNxUQCWdZsWhv7ZVfG8UUgki7LKMGPruJM97vteBS101hSCaCQz+zTyVyP8Uy nqx5zlPmcBUA92gAFfSCa+tm/lR2BY5g/20mZX/lMY0xXV1iLhfdK6RgJYXX2SdH YR/01IgmjgTfIp7gX+xixDgGZuZY++jo8C52udFkCf5vxyG4Ed57vRfCLFOPfeY4 r3i0Jiply65zSo8y/6KxudRtmGOfV2qb2EsMTW9PaLs3+rnhhiYBM/nR4V5ux6u6 DwIDAQAB -----END PUBLIC KEY----- EOD; openssl_get_publickey($publicKeyPem); var_dump(openssl_error_string()); ``` Resulted in this output: ``` string(54) "error:0906D06C:PEM routines:PEM_read_bio:no start line" ``` But I expected this output instead: ``` bool(false) ``` Note that openssl_get_publickey succeeded; the error is there nonetheless. More elaborate test cases here: https://github.com/famoser/polyas-verification/blob/a28f16437ab695548b3cb301258f87badf7aee69/tests/Utils/OpenSSLBugs.php ## Analysis The error happens when the PEM contains a public key, as it will be first tried to be parsed as a certificate (see [here](https://github.com/php/php-src/blob/c4a1100f190efce41eab728e84788d2491cc4c71/ext/openssl/openssl.c#L3574)). The parsing as a certificate fails, which then leads to a corresponding error tracked by PHP with the next call to php_openssl_store_errors(). As PHP handles the case where the certificate cannot be parsed, consequentially this error should never reach the user. Else the user-code would need to ignore this error somehow, which requires knowledge of implementation details when this is safe to do, and when not. ## Solution I see two ways how to fix the issue. ### Prevent error being stored The error should never reach PHP's OPENSSL_G(errors) buffer. Prevent php_openssl_x509_from_str to store the errors, and discard them in php_openssl_pkey_from_zval. Pseudo-code: ```C /* {{{ php_openssl_discard_errors */ void php_openssl_discard_errors(void) { int error_code; while ((error_code = ERR_get_error())); } /* }}} */ static EVP_PKEY *php_openssl_pkey_from_zval( zval *val, int public_key, char *passphrase, size_t passphrase_len, uint32_t arg_num) { // ... php_openssl_store_errors(); cert = php_openssl_x509_from_str(Z_STR_P(val), arg_num, false, NULL, true); if (cert) { free_cert = 1; } else { php_openssl_discard_errors(); // ... } } static X509 *php_openssl_x509_from_str( zend_string *cert_str, uint32_t arg_num, bool is_from_array, const char *option_name, const int store_errors) { // ... (4 times) if (store_errors) { php_openssl_store_errors(); } // ... } ``` ### Mark & Revert error Similar to how openSSL error buffer works, introduce an option to mark the buffer at a specific state, and allow to revert to this state. Pseudo-code: ```C /* {{{ php_openssl_errors_set_mark */ void php_openssl_errors_set_mark(void) { if (!OPENSSL_G(errors)) { return; } OPENSSL_G(errors_mark) = pecalloc(1, sizeof(struct php_openssl_errors), 1); memcpy(OPENSSL_G(errors), OPENSSL_G(errors_mark), sizeof(struct php_openssl_errors)); } /* }}} */ /* {{{ php_openssl_errors_restore_mark */ void php_openssl_errors_restore_mark(void) { struct php_openssl_errors *errors; if (!OPENSSL_G(errors)) { return; } if (!OPENSSL_G(errors_mark)) { errors = OPENSSL_G(errors); errors->top = 0; errors->bottom = 0; } else { memcpy(OPENSSL_G(errors_mark), OPENSSL_G(errors), sizeof(struct php_openssl_errors)); } } /* }}} */ static EVP_PKEY *php_openssl_pkey_from_zval( zval *val, int public_key, char *passphrase, size_t passphrase_len, uint32_t arg_num) { // ... php_openssl_errors_set_mark(); cert = php_openssl_x509_from_str(Z_STR_P(val), arg_num, false, NULL); if (cert) { free_cert = 1; } else { php_openssl_errors_restore_mark(); // ... } } ``` I'll create a PR shortly for this second version. I feel it is less intrusive and simpler to understand. Let me know if you have a different option on this, and I'll update the PR. ### PHP Version PHP 8.0.28, PHP 8.1.17, PHP 8.2.4 ### Operating System _No response_

« previous php.bugs (#244168) next »