Passed
Pull Request — master (#6)
by
unknown
07:17
created

CryptoProCli.php (1 issue)

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
82
        return $return;
83
    }
84
85
    /**
86
     * Добавить подпись в файл, уже содержащий подпись
87
     *
88
     * @param string $file Путь к файлу
89
     * @param string $thumbprint SHA1 отпечаток, например, bb959544444d8d9e13ca3b8801d5f7a52f91fe97
90
     * @throws Cli
91
     */
92
    public static function addSignToFile($file, $thumbprint)
93
    {
94
        $shellCommand = self::getCryptcpExec() .
95
            ' -addsign -thumbprint ' . $thumbprint . ' ' . $file;
96
        $result = shell_exec($shellCommand);
97
        if (strpos($result, "Signed message is created.") <= 0) {
98
            throw new Cli('В ответе Cryptcp не найдена строка Signed message is created: ' . $result . ' команда ' . $shellCommand);
99
        }
100
    }
101
102
    /**
103
     * Проверить, что содержимое файла подписано правильной подписью
104
     *
105
     *
106
     * @param $fileContent
107
     * @throws Cli
108
     * @throws SignatureError
109
     */
110
    public static function verifyFileContent($fileContent)
111
    {
112
        $file = tempnam(sys_get_temp_dir(), 'cpc');
113
        file_put_contents($file, $fileContent);
114
        try {
115
            self::verifyFile($file);
116
        } finally {
117
            unlink($file);
118
        }
119
    }
120
121
    /**
122
     * Проверить, что содержимое файла подписано правильной подписью открепленной подписью
123
     *
124
     *
125
     * @param $fileSignContent
126
     * @param $fileToBeSigned
127
     * @throws Cli
128
     * @throws SignatureError
129
     */
130
    public static function verifyFileContentDetached($fileSignContent, $fileToBeSignedContent)
131
    {
132
        $fileToBeSigned = tempnam(sys_get_temp_dir(), 'detach');
133
        $fileSign = $fileToBeSigned . '.sgn';
134
        file_put_contents($fileSign, $fileSignContent);
135
        file_put_contents($fileToBeSigned, $fileToBeSignedContent);
136
        try {
137
            self::verifyFileDetached($fileSign, $fileToBeSigned, sys_get_temp_dir());
138
        } finally {
139
            unlink($fileSign);
140
            unlink($fileToBeSigned);
141
        }
142
    }
143
144
    private static function getDevNull()
145
    {
146
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
147
            return 'NUL';
148
        }
149
150
        return '/dev/null';
151
    }
152
153
    const ERROR_CODE_WRONG_SIGN = '0x200001f9';
154
    const ERROR_CODE_WRONG_CHAIN = '0x20000133';
155
    const ERROR_CODE_NO_CERTS = '0x2000012d';
156
    const ERROR_CODE_MULTIPLE_CERTS = '0x2000012e';
157
    const ERROR_CODE_UNTRUSTED_ROOT = '0x20000131';
158
    const ERROR_CODE_MESSAGE = [
159
        self::ERROR_CODE_WRONG_CHAIN    => 'Цепочка сертификатов не проверена',
160
        self::ERROR_CODE_WRONG_SIGN     => 'Подпись не верна',
161
        self::ERROR_CODE_NO_CERTS       => 'Сертификаты не найдены',
162
        self::ERROR_CODE_MULTIPLE_CERTS => 'Более одного сертификата',
163
        self::ERROR_CODE_UNTRUSTED_ROOT => 'Нет доверия к корневому сертификату',
164
    ];
165
166
    /**
167
     * Проверить, что файл подписан правильной подписью
168
     *
169
     *
170
     * @param $file
171
     * @throws Cli
172
     * @throws SignatureError
173
     */
174
    public static function verifyFile($file)
175
    {
176
        $shellCommand = 'yes "n" 2> ' . self::getDevNull() . ' | ' . escapeshellarg(self::$cryptcpExec) . ' -verify -verall ' . escapeshellarg($file);
177
        $result = shell_exec($shellCommand);
178
        if (strpos($result, "[ErrorCode: 0x00000000]") === false && strpos($result, "[ReturnCode: 0]") === false) {
179
            preg_match('#\[ErrorCode: (.+)\]#', $result, $matches);
180
            $code = strtolower($matches[1]);
181
            if (isset(self::ERROR_CODE_MESSAGE[$code])) {
182
                $message = self::ERROR_CODE_MESSAGE[$code];
183
                //Дополнительная расшифровка ошибки
184
                if (strpos($result, 'The certificate or certificate chain is based on an untrusted root') !== false) {
185
                    $message .= ' - нет доверия к корневому сертификату УЦ, выпустившего эту подпись.';
186
                }
187
                throw new SignatureError($message, $code);
188
            }
189
            throw new Cli("Неожиданный результат $shellCommand: \n$result");
190
        }
191
    }
192
193
    /**
194
     * Проверить, что файл подписан правильной открепленной подписью подписью
195
     *
196
     * @param $fileSign
197
     * @param $fileToBeSigned
198
     * @param $fileDir
199
     * @throws Cli
200
     * @throws SignatureError
201
     */
202
    public static function verifyFileDetached($fileSign, $fileToBeSigned, $fileDir)
203
    {
204
        //Пример cryptcp.exe -verify y:\text.txt -detached -nochain -f y:\signature.sig -dir y:\
205
        $shellCommand = 'yes "n" 2> ' . self::getDevNull() . ' | ' . escapeshellarg(self::$cryptcpExec) . ' -vsignf -dir '
206
            . escapeshellarg($fileDir) . ' '
207
            . escapeshellarg($fileToBeSigned)
208
            . ' -f ' . escapeshellarg($fileSign);
209
        $result = shell_exec($shellCommand);
210
        if (strpos($result, "[ErrorCode: 0x00000000]") === false && strpos($result, "[ReturnCode: 0]") === false) {
211
            preg_match('#\[ErrorCode: (.+)\]#', $result, $matches);
212
            $code = strtolower($matches[1]);
213
            if (isset(self::ERROR_CODE_MESSAGE[$code])) {
214
                throw new SignatureError(self::ERROR_CODE_MESSAGE[$code], $code);
215
            }
216
            throw new Cli("Неожиданный результат $shellCommand: \n$result");
217
        }
218
    }
219
220
}
221