Passed
Push — master ( 34d0b2...ac8cb3 )
by Nikita
07:13
created

CryptoProCli::verifyFileContent()   A

Complexity

Conditions 1
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 8
rs 10
cc 1
nc 2
nop 1
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
    /**
19
     * @var bool Небезопасный режим - когда цепочка подтверждения подписи не проверяется.
20
     * Включение даст возможность использовать самоподписанные сертификаты.
21
     */
22
    public static $unsafeMode = false;
23
24
    /**
25
     * @var string Путь к исполняемому файлу Curl КриптоПро
26
     */
27
    public static $cryptcpExec = '/opt/cprocsp/bin/amd64/cryptcp';
28
29
    private static function getCryptcpExec()
30
    {
31
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
32
            return '"' . self::$cryptcpExec . '"';
33
        } else {
34
            return self::$cryptcpExec;
35
        }
36
    }
37
38
    /**
39
     * Подписать ранее неподписанный файл
40
     *
41
     * @param string $file
42
     * @param string $thumbprint
43
     * @param null   $toFile
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $toFile is correct as it would always require null to be passed?
Loading history...
44
     * @param bool $detached Создать открепленную подпись
45
     * @throws Cli
46
     */
47
    public static function signFile($file, $thumbprint, $toFile = null, $detached = false)
48
    {
49
        $shellCommand = self::getCryptcpExec() .
50
            ' -sign '.($detached ? '-detached' : '').' -thumbprint ' . $thumbprint . ' ' . $file . ' ' . $toFile;
51
        if (self::$unsafeMode) {
52
            $shellCommand = 'yes | '.$shellCommand;
53
        }
54
        $result = shell_exec($shellCommand);
55
56
        if (strpos($result, "Signed message is created.") <= 0 && strpos($result,
57
                "Подписанное сообщение успешно создано") <= 0) {
58
            throw new Cli('В ответе Cryptcp не найдена строка "Signed message is created" или "Подписанное сообщение успешно создано": ' . $result . ' команда ' . $shellCommand);
59
        }
60
    }
61
62
    /**
63
     * Подписать данные
64
     *
65
     *
66
     * @param $data
67
     * @param $thumbprint
68
     * @return bool|string
69
     * @throws Cli
70
     */
71
    public static function signData($data, $thumbprint)
72
    {
73
        $from = tempnam('/tmp', 'cpsign');
74
        $to = tempnam('/tmp', 'cpsign');
75
        file_put_contents($from, $data);
76
77
        self::signFile($from, $thumbprint, $to);
78
        unlink($from);
79
        $return = file_get_contents($to);
80
        unlink($to);
81
        return $return;
82
    }
83
84
    /**
85
     * Добавить подпись в файл, уже содержащий подпись
86
     *
87
     * @param string $file Путь к файлу
88
     * @param string $thumbprint SHA1 отпечаток, например, bb959544444d8d9e13ca3b8801d5f7a52f91fe97
89
     * @throws Cli
90
     */
91
    public static function addSignToFile($file, $thumbprint)
92
    {
93
        $shellCommand = self::getCryptcpExec() .
94
            ' -addsign -thumbprint ' . $thumbprint . ' ' . $file;
95
        $result = shell_exec($shellCommand);
96
        if (strpos($result, "Signed message is created.") <= 0) {
97
            throw new Cli('В ответе Cryptcp не найдена строка Signed message is created: ' . $result . ' команда ' . $shellCommand);
98
        }
99
    }
100
101
    /**
102
     * Проверить, что содержимое файла подписано правильной подписью
103
     *
104
     *
105
     * @param $fileContent
106
     * @throws Cli
107
     * @throws SignatureError
108
     */
109
    public static function verifyFileContent($fileContent)
110
    {
111
        $file = tempnam(sys_get_temp_dir(), 'cpc');
112
        file_put_contents($file, $fileContent);
113
        try {
114
            self::verifyFile($file);
115
        } finally {
116
            unlink($file);
117
        }
118
    }
119
120
    /**
121
     * Проверить, что содержимое файла подписано правильной подписью открепленной подписью
122
     *
123
     *
124
     * @param $fileSignContent
125
     * @param $fileToBeSigned
126
     * @throws Cli
127
     * @throws SignatureError
128
     */
129
    public static function verifyFileContentDetached($fileSignContent, $fileToBeSignedContent)
130
    {
131
        $fileToBeSigned = tempnam(sys_get_temp_dir(), 'detach');
132
        $fileSign = $fileToBeSigned . '.sgn';
133
        file_put_contents($fileSign, $fileSignContent);
134
        file_put_contents($fileToBeSigned, $fileToBeSignedContent);
135
        try {
136
            self::verifyFileDetached($fileSign, $fileToBeSigned, sys_get_temp_dir());
137
        } finally {
138
            unlink($fileSign);
139
            unlink($fileToBeSigned);
140
        }
141
    }
142
143
    private static function getDevNull()
144
    {
145
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
146
            return 'NUL';
147
        }
148
        return '/dev/null';
149
    }
150
151
    CONST ERROR_CODE_WRONG_SIGN = '0x200001f9';
152
    const ERROR_CODE_MESSAGE = [
153
        '0x20000133' => 'Цепочка сертификатов не проверена',
154
        self::ERROR_CODE_WRONG_SIGN => 'Подпись не верна',
155
        '0x2000012d' => 'Сертификаты не найдены',
156
        '0x2000012e' => 'Более одного сертификата',
157
        '0x20000131' => 'Нет доверия к корневому сертификату',
158
    ];
159
160
    /**
161
     * Проверить, что файл подписан правильной подписью
162
     *
163
     *
164
     * @param $file
165
     * @throws Cli
166
     * @throws SignatureError
167
     */
168
    public static function verifyFile($file)
169
    {
170
        $shellCommand = 'yes "n" 2> '.self::getDevNull().' | ' . escapeshellarg(self::$cryptcpExec) . ' -verify -verall ' . escapeshellarg($file);
171
        $result = shell_exec($shellCommand);
172
        if (strpos($result, "[ErrorCode: 0x00000000]") === false && strpos($result, "[ReturnCode: 0]") === false) {
173
            preg_match('#\[ErrorCode: (.+)\]#', $result, $matches);
174
            $code = strtolower($matches[1]);
175
            if (isset(self::ERROR_CODE_MESSAGE[$code])) {
176
                $message = self::ERROR_CODE_MESSAGE[$code];
177
                //Дополнительная расшифровка ошибки
178
                if (strpos($result, 'The certificate or certificate chain is based on an untrusted root') !== false) {
179
                    $message .= ' - нет доверия к корневому сертификату УЦ, выпустившего эту подпись.';
180
                }
181
                throw new SignatureError($message);
182
            }
183
            throw new Cli("Неожиданный результат $shellCommand: \n$result");
184
        }
185
    }
186
187
    /**
188
     * Проверить, что файл подписан правильной открепленной подписью подписью
189
     *
190
     * @param $fileSign
191
     * @param $fileToBeSigned
192
     * @param $fileDir
193
     * @throws Cli
194
     * @throws SignatureError
195
     */
196
    public static function verifyFileDetached($fileSign, $fileToBeSigned, $fileDir)
197
    {
198
        //Пример cryptcp.exe -verify y:\text.txt -detached -nochain -f y:\signature.sig -dir y:\
199
        $shellCommand = 'yes "n" 2> '.self::getDevNull() . ' | ' . escapeshellarg(self::$cryptcpExec) . ' -vsignf -dir '
200
            . escapeshellarg($fileDir) . ' '
201
            . escapeshellarg($fileToBeSigned)
202
            . ' -f ' . escapeshellarg($fileSign);
203
        $result = shell_exec($shellCommand);
204
        if (strpos($result, "[ErrorCode: 0x00000000]") === false && strpos($result, "[ReturnCode: 0]") === false) {
205
            preg_match('#\[ErrorCode: (.+)\]#', $result, $matches);
206
            $code = strtolower($matches[1]);
207
            if (isset(self::ERROR_CODE_MESSAGE[$code])) {
208
                throw new SignatureError(self::ERROR_CODE_MESSAGE[$code]);
209
            }
210
            throw new Cli("Неожиданный результат $shellCommand: \n$result");
211
        }
212
    }
213
214
}
215