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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
vendor
.idea
composer.phar
.DS_Store
78 changes: 73 additions & 5 deletions src/FpdiProtection.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,24 +193,33 @@ class FpdiProtection extends \setasign\Fpdi\Fpdi
*/
protected $fileIdentifier;

/**
* @var bool
*/
protected $useArcfourFallback;

/**
* FpdiProtection constructor.
*
* @param string $orientation
* @param string $unit
* @param string $size
*/
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4')
public function __construct($orientation = 'P', $unit = 'mm', $size = 'A4', $useArcfourFallback = false)
artulloss marked this conversation as resolved.
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.

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

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)

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 with RC4 supported is required if $useArcfourFallback is false.' .
'If using OpenSSL 3 make sure that legacy providers are loaded ' .
'(see https://wiki.openssl.org/index.php/OpenSSL_3.0#Providers).'
);
}
}
Expand Down Expand Up @@ -509,7 +518,39 @@ protected function computeUValue($encryptionKey)
*/
protected function arcfour($key, $data)
{
return openssl_encrypt($data, 'RC4-40', $key, OPENSSL_RAW_DATA, '');
if (!$this->useArcfourFallback) {
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;
}

/**
Expand Down Expand Up @@ -646,4 +687,31 @@ protected function _puttrailer()
$this->_put('/ID [<' . $fileIdentifier . '><' . $fileIdentifier . '>]');
}
}

protected function getOpensslVersionNumber($patchAsNumber = false, $opensslVersionNumber = null) {
artulloss marked this conversation as resolved.
Show resolved Hide resolved
if (is_null($opensslVersionNumber)) $opensslVersionNumber = OPENSSL_VERSION_NUMBER;
$opensslNumericIdentifier = str_pad((string)dechex($opensslVersionNumber),8,'0',STR_PAD_LEFT);

$opensslVersionParsed = array();
$preg = '/(?<major>[[:xdigit:]])(?<minor>[[:xdigit:]][[:xdigit:]])(?<fix>[[:xdigit:]][[:xdigit:]])';
$preg.= '(?<patch>[[:xdigit:]][[:xdigit:]])(?<type>[[:xdigit:]])/';
preg_match_all($preg, $opensslNumericIdentifier, $opensslVersionParsed);
$opensslVersion = false;
if (!empty($opensslVersionParsed)) {
$alphabet = array(1=>'a',2=>'b',3=>'c',4=>'d',5=>'e',6=>'f',7=>'g',8=>'h',9=>'i',10=>'j',11=>'k',
12=>'l',13=>'m',14=>'n',15=>'o',16=>'p',17=>'q',18=>'r',19=>'s',20=>'t',21=>'u',
22=>'v',23=>'w',24=>'x',25=>'y',26=>'z');
$opensslVersion = intval($opensslVersionParsed['major'][0]).'.';
$opensslVersion.= intval($opensslVersionParsed['minor'][0]).'.';
$opensslVersion.= intval($opensslVersionParsed['fix'][0]);
$patchlevelDec = hexdec($opensslVersionParsed['patch'][0]);
if (!$patchAsNumber && array_key_exists($patchlevelDec, $alphabet)) {
$opensslVersion.= $alphabet[$patchlevelDec]; // ideal for text comparison
}
else {
$opensslVersion.= '.'.$patchlevelDec; // ideal for version_compare
}
}
return $opensslVersion;
}
}