PageRenderTime 56ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/application/third_party/vendor/mpdf/mpdf/src/Pdf/Protection.php

https://gitlab.com/devdoblea/factutextil.local
PHP | 360 lines | 226 code | 57 blank | 77 comment | 34 complexity | 27571bbd9ba798dee96f1a6bdf4764f5 MD5 | raw file
  1. <?php
  2. namespace Mpdf\Pdf;
  3. use Mpdf\Pdf\Protection\UniqidGenerator;
  4. class Protection
  5. {
  6. /**
  7. * @var string
  8. */
  9. private $lastRc4Key;
  10. /**
  11. * @var string
  12. */
  13. private $lastRc4KeyC;
  14. /**
  15. * @var bool
  16. */
  17. private $useRC128Encryption;
  18. /**
  19. * @var string
  20. */
  21. private $encryptionKey;
  22. /**
  23. * @var string
  24. */
  25. private $padding;
  26. /**
  27. * @var string
  28. */
  29. private $uniqid;
  30. /**
  31. * @var string
  32. */
  33. private $oValue;
  34. /**
  35. * @var string
  36. */
  37. private $uValue;
  38. /**
  39. * @var string
  40. */
  41. private $pValue;
  42. /**
  43. * @var int[] Array of permission => byte representation
  44. */
  45. private $options;
  46. /**
  47. * @var \Mpdf\Pdf\Protection\UniqidGenerator
  48. */
  49. private $uniqidGenerator;
  50. public function __construct(UniqidGenerator $uniqidGenerator)
  51. {
  52. if (!function_exists('random_int') || !function_exists('random_bytes')) {
  53. throw new \Mpdf\MpdfException(
  54. 'Unable to set PDF file protection, CSPRNG Functions are not available. '
  55. . 'Use paragonie/random_compat polyfill or upgrade to PHP 7.'
  56. );
  57. }
  58. $this->uniqidGenerator = $uniqidGenerator;
  59. $this->lastRc4Key = '';
  60. $this->padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08" .
  61. "\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A";
  62. $this->useRC128Encryption = false;
  63. $this->options = [
  64. 'print' => 4, // bit 3
  65. 'modify' => 8, // bit 4
  66. 'copy' => 16, // bit 5
  67. 'annot-forms' => 32, // bit 6
  68. 'fill-forms' => 256, // bit 9
  69. 'extract' => 512, // bit 10
  70. 'assemble' => 1024, // bit 11
  71. 'print-highres' => 2048 // bit 12
  72. ];
  73. }
  74. /**
  75. * @param array $permissions
  76. * @param string $user_pass
  77. * @param string $owner_pass
  78. * @param int $length
  79. *
  80. * @return bool
  81. */
  82. public function setProtection($permissions = [], $user_pass = '', $owner_pass = null, $length = 40)
  83. {
  84. if (is_string($permissions) && strlen($permissions) > 0) {
  85. $permissions = [$permissions];
  86. } elseif (!is_array($permissions)) {
  87. return false;
  88. }
  89. $protection = $this->getProtectionBitsFromOptions($permissions);
  90. if ($length === 128) {
  91. $this->useRC128Encryption = true;
  92. } elseif ($length !== 40) {
  93. throw new \Mpdf\MpdfException('PDF protection only allows lenghts of 40 or 128');
  94. }
  95. if ($owner_pass === null) {
  96. $owner_pass = bin2hex(random_bytes(23));
  97. }
  98. $this->generateEncryptionKey($user_pass, $owner_pass, $protection);
  99. return true;
  100. }
  101. /**
  102. * Compute key depending on object number where the encrypted data is stored
  103. *
  104. * @param int $n
  105. *
  106. * @return string
  107. */
  108. public function objectKey($n)
  109. {
  110. if ($this->useRC128Encryption) {
  111. $len = 16;
  112. } else {
  113. $len = 10;
  114. }
  115. return substr($this->md5toBinary($this->encryptionKey . pack('VXxx', $n)), 0, $len);
  116. }
  117. /**
  118. * RC4 is the standard encryption algorithm used in PDF format
  119. *
  120. * @param string $key
  121. * @param string $text
  122. *
  123. * @return string
  124. */
  125. public function rc4($key, $text)
  126. {
  127. if ($this->lastRc4Key != $key) {
  128. $k = str_repeat($key, 256 / strlen($key) + 1);
  129. $rc4 = range(0, 255);
  130. $j = 0;
  131. for ($i = 0; $i < 256; $i++) {
  132. $t = $rc4[$i];
  133. $j = ($j + $t + ord($k[$i])) % 256;
  134. $rc4[$i] = $rc4[$j];
  135. $rc4[$j] = $t;
  136. }
  137. $this->lastRc4Key = $key;
  138. $this->lastRc4KeyC = $rc4;
  139. } else {
  140. $rc4 = $this->lastRc4KeyC;
  141. }
  142. $len = strlen($text);
  143. $a = 0;
  144. $b = 0;
  145. $out = '';
  146. for ($i = 0; $i < $len; $i++) {
  147. $a = ($a + 1) % 256;
  148. $t = $rc4[$a];
  149. $b = ($b + $t) % 256;
  150. $rc4[$a] = $rc4[$b];
  151. $rc4[$b] = $t;
  152. $k = $rc4[($rc4[$a] + $rc4[$b]) % 256];
  153. $out .= chr(ord($text[$i]) ^ $k);
  154. }
  155. return $out;
  156. }
  157. /**
  158. * @return mixed
  159. */
  160. public function getUseRC128Encryption()
  161. {
  162. return $this->useRC128Encryption;
  163. }
  164. /**
  165. * @return mixed
  166. */
  167. public function getUniqid()
  168. {
  169. return $this->uniqid;
  170. }
  171. /**
  172. * @return mixed
  173. */
  174. public function getOValue()
  175. {
  176. return $this->oValue;
  177. }
  178. /**
  179. * @return mixed
  180. */
  181. public function getUValue()
  182. {
  183. return $this->uValue;
  184. }
  185. /**
  186. * @return mixed
  187. */
  188. public function getPValue()
  189. {
  190. return $this->pValue;
  191. }
  192. private function getProtectionBitsFromOptions($permissions)
  193. {
  194. // bit 31 = 1073741824
  195. // bit 32 = 2147483648
  196. // bits 13-31 = 2147479552
  197. // bits 13-32 = 4294963200 + 192 = 4294963392
  198. $protection = 4294963392; // bits 7, 8, 13-32
  199. foreach ($permissions as $permission) {
  200. if (!isset($this->options[$permission])) {
  201. throw new \Mpdf\MpdfException(sprintf('Invalid permission type "%s"', $permission));
  202. }
  203. if ($this->options[$permission] > 32) {
  204. $this->useRC128Encryption = true;
  205. }
  206. if (isset($this->options[$permission])) {
  207. $protection += $this->options[$permission];
  208. }
  209. }
  210. return $protection;
  211. }
  212. private function oValue($user_pass, $owner_pass)
  213. {
  214. $tmp = $this->md5toBinary($owner_pass);
  215. if ($this->useRC128Encryption) {
  216. for ($i = 0; $i < 50; ++$i) {
  217. $tmp = $this->md5toBinary($tmp);
  218. }
  219. }
  220. if ($this->useRC128Encryption) {
  221. $keybytelen = (128 / 8);
  222. } else {
  223. $keybytelen = (40 / 8);
  224. }
  225. $owner_rc4_key = substr($tmp, 0, $keybytelen);
  226. $enc = $this->rc4($owner_rc4_key, $user_pass);
  227. if ($this->useRC128Encryption) {
  228. $len = strlen($owner_rc4_key);
  229. for ($i = 1; $i <= 19; ++$i) {
  230. $key = '';
  231. for ($j = 0; $j < $len; ++$j) {
  232. $key .= chr(ord($owner_rc4_key{$j}) ^ $i);
  233. }
  234. $enc = $this->rc4($key, $enc);
  235. }
  236. }
  237. return $enc;
  238. }
  239. private function uValue()
  240. {
  241. if ($this->useRC128Encryption) {
  242. $tmp = $this->md5toBinary($this->padding . $this->hexToString($this->uniqid));
  243. $enc = $this->rc4($this->encryptionKey, $tmp);
  244. $len = strlen($tmp);
  245. for ($i = 1; $i <= 19; ++$i) {
  246. $key = '';
  247. for ($j = 0; $j < $len; ++$j) {
  248. $key .= chr(ord($this->encryptionKey{$j}) ^ $i);
  249. }
  250. $enc = $this->rc4($key, $enc);
  251. }
  252. $enc .= str_repeat("\x00", 16);
  253. return substr($enc, 0, 32);
  254. } else {
  255. return $this->rc4($this->encryptionKey, $this->padding);
  256. }
  257. }
  258. private function generateEncryptionKey($user_pass, $owner_pass, $protection)
  259. {
  260. // Pad passwords
  261. $user_pass = substr($user_pass . $this->padding, 0, 32);
  262. $owner_pass = substr($owner_pass . $this->padding, 0, 32);
  263. $this->oValue = $this->oValue($user_pass, $owner_pass);
  264. $this->uniqid = $this->uniqidGenerator->generate();
  265. // Compute encyption key
  266. if ($this->useRC128Encryption) {
  267. $keybytelen = (128 / 8);
  268. } else {
  269. $keybytelen = (40 / 8);
  270. }
  271. $prot = sprintf('%032b', $protection);
  272. $perms = chr(bindec(substr($prot, 24, 8)));
  273. $perms .= chr(bindec(substr($prot, 16, 8)));
  274. $perms .= chr(bindec(substr($prot, 8, 8)));
  275. $perms .= chr(bindec(substr($prot, 0, 8)));
  276. $tmp = $this->md5toBinary($user_pass . $this->oValue . $perms . $this->hexToString($this->uniqid));
  277. if ($this->useRC128Encryption) {
  278. for ($i = 0; $i < 50; ++$i) {
  279. $tmp = $this->md5toBinary(substr($tmp, 0, $keybytelen));
  280. }
  281. }
  282. $this->encryptionKey = substr($tmp, 0, $keybytelen);
  283. $this->uValue = $this->uValue();
  284. $this->pValue = $protection;
  285. }
  286. private function md5toBinary($string)
  287. {
  288. return pack('H*', md5($string));
  289. }
  290. private function hexToString($hs)
  291. {
  292. $s = '';
  293. $len = strlen($hs);
  294. if (($len % 2) != 0) {
  295. $hs .= '0';
  296. ++$len;
  297. }
  298. for ($i = 0; $i < $len; $i += 2) {
  299. $s .= chr(hexdec($hs{$i} . $hs{($i + 1)}));
  300. }
  301. return $s;
  302. }
  303. }