1 | <?php |
||||||
2 | |||||||
3 | namespace nikserg\cryptoprocli; |
||||||
4 | |||||||
5 | use nikserg\cryptoprocli\Exception\Cli; |
||||||
6 | use nikserg\cryptoprocli\Exception\SignatureError; |
||||||
7 | |||||||
8 | /** |
||||||
9 | * Class CryptoProCli |
||||||
10 | * |
||||||
11 | * Функции для работы с консольной утилитой КриптоПро |
||||||
12 | * |
||||||
13 | * @package nikserg\cryptoprocli |
||||||
14 | */ |
||||||
15 | class CryptoProCli |
||||||
16 | { |
||||||
17 | /** |
||||||
18 | * @var string Путь к исполняемому файлу Curl КриптоПро |
||||||
19 | */ |
||||||
20 | public static $cryptcpExec = '/opt/cprocsp/bin/amd64/cryptcp'; |
||||||
21 | |||||||
22 | private static function getCryptcpExec() |
||||||
23 | { |
||||||
24 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { |
||||||
25 | return '"' . self::$cryptcpExec . '"'; |
||||||
26 | } else { |
||||||
27 | return self::$cryptcpExec; |
||||||
28 | } |
||||||
29 | } |
||||||
30 | |||||||
31 | /** |
||||||
32 | * Подписать ранее неподписанный файл |
||||||
33 | * |
||||||
34 | * @param string $file |
||||||
35 | * @param string $thumbprint |
||||||
36 | * @param null $toFile |
||||||
37 | * @throws Cli |
||||||
38 | */ |
||||||
39 | public static function signFile($file, $thumbprint, $toFile = null) |
||||||
40 | { |
||||||
41 | $shellCommand = self::getCryptcpExec() . |
||||||
42 | ' -sign -thumbprint ' . $thumbprint . ' ' . $file . ' ' . $toFile; |
||||||
43 | $result = shell_exec($shellCommand); |
||||||
44 | |||||||
45 | if (strpos($result, "Signed message is created.") <= 0 && strpos($result, |
||||||
46 | "Подписанное сообщение успешно создано") <= 0) { |
||||||
47 | throw new Cli('В ответе Cryptcp не найдена строка "Signed message is created" или "Подписанное сообщение успешно создано": ' . $result . ' команда ' . $shellCommand); |
||||||
48 | } |
||||||
49 | } |
||||||
50 | |||||||
51 | /** |
||||||
52 | * Подписать данные |
||||||
53 | * |
||||||
54 | * |
||||||
55 | * @param $data |
||||||
56 | * @param $thumbprint |
||||||
57 | * @return bool|string |
||||||
58 | * @throws Cli |
||||||
59 | */ |
||||||
60 | public static function signData($data, $thumbprint) |
||||||
61 | { |
||||||
62 | $from = tempnam('/tmp', 'cpsign'); |
||||||
63 | $to = tempnam('/tmp', 'cpsign'); |
||||||
64 | file_put_contents($from, $data); |
||||||
65 | |||||||
66 | self::signFile($from, $thumbprint, $to); |
||||||
67 | unlink($from); |
||||||
68 | $return = file_get_contents($to); |
||||||
69 | unlink($to); |
||||||
70 | return $return; |
||||||
71 | } |
||||||
72 | |||||||
73 | /** |
||||||
74 | * Добавить подпись в файл, уже содержащий подпись |
||||||
75 | * |
||||||
76 | * @param string $file Путь к файлу |
||||||
77 | * @param string $thumbprint SHA1 отпечаток, например, bb959544444d8d9e13ca3b8801d5f7a52f91fe97 |
||||||
78 | * @throws Cli |
||||||
79 | */ |
||||||
80 | public static function addSignToFile($file, $thumbprint) |
||||||
81 | { |
||||||
82 | $shellCommand = self::getCryptcpExec() . |
||||||
83 | ' -addsign -thumbprint ' . $thumbprint . ' ' . $file; |
||||||
84 | $result = shell_exec($shellCommand); |
||||||
85 | if (strpos($result, "Signed message is created.") <= 0) { |
||||||
86 | throw new Cli('В ответе Cryptcp не найдена строка Signed message is created: ' . $result . ' команда ' . $shellCommand); |
||||||
87 | } |
||||||
88 | } |
||||||
89 | |||||||
90 | /** |
||||||
91 | * Проверить, что содержимое файла подписано правильной подписью |
||||||
92 | * |
||||||
93 | * |
||||||
94 | * @param $fileContent |
||||||
95 | * @throws Cli |
||||||
96 | * @throws SignatureError |
||||||
97 | */ |
||||||
98 | public static function verifyFileContent($fileContent) |
||||||
99 | { |
||||||
100 | $file = tempnam(sys_get_temp_dir(), 'cpc'); |
||||||
101 | file_put_contents($file, $fileContent); |
||||||
102 | try { |
||||||
103 | self::verifyFile($file); |
||||||
104 | } finally { |
||||||
105 | unlink($file); |
||||||
106 | } |
||||||
107 | } |
||||||
108 | |||||||
109 | /** |
||||||
110 | * Проверить, что содержимое файла подписано правильной подписью открепленной подписью |
||||||
111 | * |
||||||
112 | * |
||||||
113 | * @param $fileSignContent |
||||||
114 | * @param $fileToBeSigned |
||||||
115 | * @throws Cli |
||||||
116 | * @throws SignatureError |
||||||
117 | */ |
||||||
118 | public static function verifyFileContentDetached($fileSignContent, $fileToBeSignedContent) |
||||||
119 | { |
||||||
120 | $fileToBeSigned = tempnam(sys_get_temp_dir(), 'detach'); |
||||||
121 | $fileSign .= $fileToBeSigned . '.sgn'; |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
![]() |
|||||||
122 | file_put_contents($fileSign, $fileSignContent); |
||||||
123 | file_put_contents($fileToBeSigned, $fileToBeSignedContent); |
||||||
124 | try { |
||||||
125 | self::verifyFileDetached($fileSign, $fileToBeSigned); |
||||||
0 ignored issues
–
show
The call to
nikserg\cryptoprocli\Cry...i::verifyFileDetached() has too few arguments starting with fileDir .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
126 | } finally { |
||||||
127 | unlink($fileSign); |
||||||
128 | unlink($fileToBeSigned); |
||||||
129 | } |
||||||
130 | } |
||||||
131 | |||||||
132 | private static function getDevNull() |
||||||
133 | { |
||||||
134 | if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { |
||||||
135 | return 'NUL'; |
||||||
136 | } |
||||||
137 | return '/dev/null'; |
||||||
138 | } |
||||||
139 | |||||||
140 | const ERROR_CODE_MESSAGE = [ |
||||||
141 | '0x20000133' => 'Цепочка сертификатов не проверена', |
||||||
142 | '0x200001f9' => 'Подпись не верна', |
||||||
143 | '0x2000012d' => 'Сетификаты не найдены', |
||||||
144 | '0x2000012e' => 'Более одного сертификата', |
||||||
145 | ]; |
||||||
146 | |||||||
147 | /** |
||||||
148 | * Проверить, что файл подписан правильной подписью |
||||||
149 | * |
||||||
150 | * |
||||||
151 | * @param $file |
||||||
152 | * @throws Cli |
||||||
153 | * @throws SignatureError |
||||||
154 | */ |
||||||
155 | public static function verifyFile($file) |
||||||
156 | { |
||||||
157 | $shellCommand = 'yes "n" 2> '.self::getDevNull().' | ' . escapeshellarg(self::$cryptcpExec) . ' -verify -verall ' . escapeshellarg($file); |
||||||
158 | $result = shell_exec($shellCommand); |
||||||
159 | if (strpos($result, "[ErrorCode: 0x00000000]") === false && strpos($result, "[ReturnCode: 0]") === false) { |
||||||
160 | preg_match('#\[ErrorCode: (.+)\]#', $result, $matches); |
||||||
161 | $code = strtolower($matches[1]); |
||||||
162 | if (isset(self::ERROR_CODE_MESSAGE[$code])) { |
||||||
163 | throw new SignatureError(self::ERROR_CODE_MESSAGE[$code]); |
||||||
164 | } |
||||||
165 | throw new Cli("Неожиданный результат $shellCommand: \n$result"); |
||||||
166 | } |
||||||
167 | } |
||||||
168 | |||||||
169 | /** |
||||||
170 | * Проверить, что файл подписан правильной открепленной подписью подписью |
||||||
171 | * |
||||||
172 | * @param $fileSign |
||||||
173 | * @param $fileToBeSigned |
||||||
174 | * @param $fileDir |
||||||
175 | * @throws Cli |
||||||
176 | * @throws SignatureError |
||||||
177 | */ |
||||||
178 | public static function verifyFileDetached($fileSign, $fileToBeSigned, $fileDir) |
||||||
179 | { |
||||||
180 | //Пример cryptcp.exe -verify y:\text.txt -detached -nochain -f y:\signature.sig -dir y:\ |
||||||
181 | $shellCommand = 'yes "n" 2> '.self::getDevNull() . ' | ' . escapeshellarg(self::$cryptcpExec) . ' -vsignf -dir ' |
||||||
182 | . escapeshellarg($fileDir) . ' ' |
||||||
183 | . escapeshellarg($fileSign) |
||||||
184 | . ' -f ' . escapeshellarg($fileToBeSigned); |
||||||
185 | $result = shell_exec($shellCommand); |
||||||
186 | if (strpos($result, "[ErrorCode: 0x00000000]") === false && strpos($result, "[ReturnCode: 0]") === false) { |
||||||
187 | preg_match('#\[ErrorCode: (.+)\]#', $result, $matches); |
||||||
188 | $code = strtolower($matches[1]); |
||||||
189 | if (isset(self::ERROR_CODE_MESSAGE[$code])) { |
||||||
190 | throw new SignatureError(self::ERROR_CODE_MESSAGE[$code]); |
||||||
191 | } |
||||||
192 | throw new Cli("Неожиданный результат $shellCommand: \n$result"); |
||||||
193 | } |
||||||
194 | } |
||||||
195 | |||||||
196 | } |
||||||
197 |