Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 14, OpenSSL RC4 #19

Merged
merged 13 commits into from
Oct 20, 2023
Merged

Issue 14, OpenSSL RC4 #19

merged 13 commits into from
Oct 20, 2023

Conversation

artulloss
Copy link
Contributor

This creates a new parameter in the constructor to opt to not use OpenSSL and instead use a native PHP implementation.

@JanSlabon
Copy link
Member

We had such fallback in place if OpenSSL was not installed at all - but it is simply slow as hell compared to OpenSSL. At some time we left this fallback, which worked for years now.

The problem you try to solve by adding this fallback is that you or your system admin had installed an incompatible version of OpenSSL to your PHP version. I would suggest to fix this at this place instead of silencing it by using a native slow fallback solution.

@artulloss
Copy link
Contributor Author

@wpengine is shipping OpenSSL 3 on PHP 7.4 builds as part of their ongoing security updates. I brought it up to their engineering team that it's incompatible and breaking this library but they didn't seem to care. They comprise 51% of the managed WordPress hosting market so a significant number of websites probably have this problem with OpenSSL.

I did not notice a terrible delay in the time it took to generate the PDF, but even if this is the case, generating a protected PDF is likely not something going to be done frequently enough to be a performance bottleneck. While this is entirely situational, I think a fallback that can be manually enabled after receiving a warning is useful, but I understand your reasoning for not wanting to allow for that.

@JanSlabon
Copy link
Member

They setup an environment which is incompatible (per documentation) and ignore this? Strange... At the end it's not only FPDI Protection that doesn't work but any other tool that relies on OpenSSL. The behavior is completely broken or undefined by doing this.

Would it be the right way to silence that your environment is broken?

We've currently no access to such a faulty environment but I would go for the fallback if:

The parameter $useArcfourFallback (please name it that way) is set and:
a) the algorithm is not supported (!in_array('rc4-40', openssl_get_cipher_methods(), true))
or
b) OpenSSL 3 is installed on PHP < 8.1

Otherwise we should throw the current exception (maybe with a more details message).

Please use following arcfour()-method (it is tested as it is shipped with our other PDF solutions for several years now):

public static function arcfour($key, $data)
{
    if ($this->opensslCipherSupported) {
        return openssl_encrypt($data, 'rc4-40', $key,  OPENSSL_RAW_DATA, '');
    }
    
    static $_lastRc4Key = null, $_lastRc4KeyValue = null;

    if ($_lastRc4Key !== $key) {
        $k = str_repeat($key, (int)(256 / strlen($key) + 1));
        $rc4 = range(0, 255);
        $j = 0;
        for ($i = 0; $i < 256; $i++) {
            $rc4[$i] = $rc4[$j = ($j + ($t = $rc4[$i]) + ord($k[$i])) % 256];
            $rc4[$j] = $t;
        }
        $_lastRc4Key = $key;
        $_lastRc4KeyValue = $rc4;

    } else {
        $rc4 = $_lastRc4KeyValue;
    }

    $len = strlen($data);
    $newData = '';
    $a = 0;
    $b = 0;
    for ($i = 0; $i < $len; $i++) {
        $b = ($b + ($t = $rc4[$a = ($a + 1) % 256])) % 256;
        $rc4[$a] = $rc4[$b];
        $rc4[$b] = $t;
        $newData .= chr(ord($data[$i]) ^ $rc4[($rc4[$a] + $rc4[$b]) % 256]);
    }

    return $newData;
}

Regarding speed: It will be noticeable if you process files with e.g. big images.

… with OpenSSL, or OpenSSL does not support RC4. Uses setasign implementation of RC4
@JanSlabon
Copy link
Member

Okay, thanks for the update. We just had an internal conversation about this and came to the conclusion that a silence fallback should not happen. So if the parameter is set to true, the fallback should be used throughout.

If it is set to false and "openssl is not installed" or the "rc4 cipher is not available" or "an incompatible version construct" is installed an exception should be thrown.

The current PHP version comparsion looks wrong, or?
Can you share a link with information about the OPENSSL_VERSION_NUMBER constant?

@JanSlabon
Copy link
Member

This becomes more complex than I ever thought. Isn't there an easier way to check for the OpenSSL version?
From my experience the issue was only triggered with OpenSSL 3. We never ever had such problem before. So for me it would be okay, if you only check for this one specific version problem.

@artulloss
Copy link
Contributor Author

artulloss commented Oct 10, 2023

Honestly that'd be fine. We could also ignore the version number checking entirely because the RC4 algorithm isn't supported in the bad environment. I believe this is because legacy providers are not loaded, however it wasn't throwing an error before, just output with a password to view that didn't align with the password that was set. Let me know what you'd like to go with.

@artulloss artulloss marked this pull request as draft October 10, 2023 14:31
@JanSlabon
Copy link
Member

From my experience the faulty environment is "lying" about the supported cipher. That's why I think we need the version comparison. Can you confirm that? What's in_array('rc4-40', openssl_get_cipher_methods(), true) returning in a faulty environment?

@artulloss
Copy link
Contributor Author

artulloss commented Oct 10, 2023

This was what my debugging data looked like on this last one. It is false, but I believe this may vary depending on if OpenSSL 3 legacy providers are loaded.

array(5) {
["!in_array('rc4-40', openssl_get_cipher_methods(), true)"]=> bool(false)
["!function_exists('openssl_encrypt')"]=> bool(false)
["PHP Version ID]=> int(70433)
["OpenSSL Version"]=> int(805306400)
["isOpensslCompatibleWithPhp7"]=> bool(false)
}

@JanSlabon
Copy link
Member

Isn't this negated:

["!in_array('rc4-40', openssl_get_cipher_methods(), true)"]=> bool(false)

? Without negation it would be true which means that it is lying, or?

@artulloss
Copy link
Contributor Author

Oh, you're right, it does appear to be lying. I think the version check is good then, and the latest one only applies to this specific issue.

@artulloss artulloss marked this pull request as ready for review October 10, 2023 15:38
src/FpdiProtection.php Outdated Show resolved Hide resolved
{
parent::__construct($orientation, $unit, $size);

// PHP 7.1-8.0 requires OpenSSL >= 1.0.1, < 3.0
$isOpensslCompatibleWithPhp7 = PHP_VERSION_ID < 70100 || PHP_VERSION_ID > 80100 || OPENSSL_VERSION_NUMBER >= 0x10001010 && OPENSSL_VERSION_NUMBER < 0x30000000;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such variable naming is strange. Please simply make a check for OpenSSL 3 + an incompatible PHP version and if $useArcFourFallback is set to false throw an Exception with an appropriate error message.


if (!function_exists('openssl_encrypt') || !in_array('rc4-40', openssl_get_cipher_methods(), true)) {
if (!$useArcfourFallback && (!function_exists('openssl_encrypt') || !in_array('rc4-40', openssl_get_cipher_methods(), true) || !$isOpensslCompatibleWithPhp7)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not throw this exception if there's a version compatibility issue in OpenSSL 3 and PHP (just handle this earlier and separated)

@JanSlabon
Copy link
Member

If an incompatible construct is recognized and $useArcfourFallback is set to false, we should throw an exception with an appropriated error message, to let the dev known that their environment is faulty (I already commented this earlier).

The later exception should only be thrown if OpenSSL is or the cipher is not installed and $useArcfourFallback is set to false.


if (!function_exists('openssl_encrypt') || !in_array('rc4-40', openssl_get_cipher_methods(), true)) {
if(OPENSSL_VERSION_NUMBER >= 0x30000000 && (PHP_VERSION_ID < 80200 || PHP_VERSION_ID >= 70100)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the "OpenSSL 3" simply incompatible to PHP versions below 8.1? Why a do you check for 8.2 and 7.1? See https://www.php.net/manual/en/openssl.requirements.php ?

Please also add a space: if (OPENSSL_....

throw new \RuntimeException(
'OpenSSL with RC4 supported is required. In case you use OpenSSL 3 make sure that ' .
'legacy providers are loaded (see https://wiki.openssl.org/index.php/OpenSSL_3.0#Providers).'
'OpenSSL 3 is not supported with PHP versions < 8.2.0 and >= 7.1.0. ' .
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

);
}

if(!(function_exists('openssl_encrypt') && in_array('rc4-40', openssl_get_cipher_methods(), true))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a space if (!function_...

{
parent::__construct($orientation, $unit, $size);

$randomBytes = function_exists('random_bytes') ? \random_bytes(32) : \mt_rand();
$this->fileIdentifier = md5(__FILE__ . PHP_SAPI . PHP_VERSION . $randomBytes, true);
$this->useArcfourFallback = $useArcfourFallback;

if($useArcfourFallback) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a space if ($useArcfourFallback) and use brackets around the return; statement.

@JanSlabon
Copy link
Member

Sorry for the delayed feedback but I was ill.

@JanSlabon JanSlabon merged commit 6d972c8 into Setasign:master Oct 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants