Ошибка cURL 60 при авторизации через ЕСИА
Делаю авторизацию на сайте через госуслуги с помощью пакета https://github.com/ekapusta/oauth2-esia. Так же использую docker с контейнерами:
- php-fpm - на нем лежит проект на Laravel;
- nginx;
- php-fpm с поддержкой алгоритмов шифрования ГОСТ 2012 - на нем крутится пакет ekapusta/oauth2-esia, к которому происходит обращение из проекта Laravel через Guzzle. Dockerfile'ы контейнеров следующие:
- php-fpm
FROM php:7.4-fpm
# Установка софта
RUN apt-get update && apt-get -y install mc vim zlib1g zlib1g-dev libpng-dev libjpeg62-turbo-dev mariadb-client \
libfreetype6-dev netcat supervisor libcurl4-openssl-dev libzip-dev \
&& docker-php-ext-install bcmath && docker-php-ext-enable bcmath \
&& docker-php-ext-install mysqli && docker-php-ext-enable mysqli \
&& docker-php-ext-install pdo && docker-php-ext-enable pdo \
&& docker-php-ext-install pdo_mysql && docker-php-ext-enable pdo_mysql \
&& pecl install xdebug && docker-php-ext-enable xdebug \
&& pecl install redis && docker-php-ext-enable redis \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-install sockets \
&& docker-php-ext-install zip
# Configure non-root user.
ARG PUID=1000
ENV PUID ${PUID}
ARG PGID=1000
ENV PGID ${PGID}
RUN groupmod -o -g ${PGID} www-data && \
usermod -o -u ${PUID} -g www-data www-data
# Конфиги
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
COPY ./conf.d/custom.ini $PHP_INI_DIR/conf.d/
COPY ./conf.d/xdebug.ini $PHP_INI_DIR/conf.d/
COPY ./conf.d/litn-backend.conf /usr/local/etc/php-fpm.d/www.conf
COPY ./supervisor.config/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY ./supervisor.config/default-worker.conf /etc/supervisor/conf.d/default-worker.conf
COPY ./supervisor.config/email-worker.conf /etc/supervisor/conf.d/email-worker.conf
COPY ./supervisor.config/orders-worker.conf /etc/supervisor/conf.d/orders-worker.conf
COPY ./wait-for-it.sh /wait-for-it.sh
RUN chmod a+x /wait-for-it.sh
COPY ./cryptopro/linux-amd64_deb/ /home/cryptopro/
RUN /home/cryptopro/install.sh
COPY --from=composer /usr/bin/composer /usr/bin/composer
- php-fpm с ГОСТ 2012
FROM php:7.4-fpm
WORKDIR /var/www
RUN apt-get update && apt-get install -y nginx libzip-dev cmake git libssl-dev \
&& docker-php-ext-install zip
COPY ./nginx/secure.conf /etc/nginx/secure.conf
COPY ./nginx/default /etc/nginx/sites-available/default
ADD https://curl.se/ca/cacert.pem /etc/ca-certificates/cacert.crt
RUN chmod 644 /etc/ca-certificates/cacert.crt
RUN touch ${PHP_INI_DIR}/conf.d/curl.ini
RUN echo curl.cainfo="/etc/ca-certificates/cacert.crt" >> ${PHP_INI_DIR}/conf.d/curl.ini
RUN echo openssl.cafile="/etc/ca-certificates/cacert.crt" >> ${PHP_INI_DIR}/conf.d/curl.ini
RUN update-ca-certificates
RUN php -r "copy('http://getcomposer.org/installer', 'composer-setup.php');" && \
php -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" && \
php composer-setup.php && \
php -r "unlink('composer-setup.php');"
# создаем файл логов
RUN touch esia.log
#### СТАВИМ Gost Engine для OpenSSL
RUN git clone --branch=openssl_1_1_0 https://github.com/gost-engine/engine.git gost-engine/engine \
&& cd gost-engine/engine && cmake . && make && cp bin/gost.so /usr/lib/x86_64-linux-gnu/engines-1.1
COPY ./openssl.cnf /etc/ssl/openssl.cnf
###############
COPY entrypoint.sh /etc/entrypoint.sh
RUN chmod o+x /etc/entrypoint.sh
EXPOSE 80
ENTRYPOINT ["/etc/entrypoint.sh"]
Алгоритм запроса следующий:
- пользователь переходит по ссылке на регистрацию через ЕСИА, за это отвечает метод в контроллере который использует сервис, который генерирует ссылку для авторизации:
public function buildAuthLink()
{
$client = new Client();
$response = $client->get($this->getLinkUrl, [
'query' => [
'client_id' => $this->appId,
'redirect_uri' => $this->redirectUri,
'remote_url' => $this->remoteUrl,
]
]);
$body = json_decode($response->getBody(), true);
$link = $body['link'];
$this->state = $body['state'];
dd($link);
return $link;
}
здесь происходит get запрос на контейнер с php-fpm с ГОСТ 2012, на файл start.php:
$certPath = getenv('CERT_PATH');
$privateKeyPath = getenv('KEY_PATH');
$clientId = $_GET['client_id'];
$redirectUri = $_GET['redirect_uri'];
$remoteUrl = $_GET['remote_url'];
$provider = new EsiaProvider(
[
'clientId' => $clientId,
'redirectUri' => $redirectUri,
'defaultScopes' => ['birthdate', 'gender', 'email', 'mobile'],
'remoteUrl' => $remoteUrl,
'remotePublicKey' => EsiaProvider::RESOURCES.'esia.test.public.key',
'remoteCertificatePath' => EsiaProvider::RESOURCES.'esia.gost.prod.public.key',
],
[
'signer' => new OpensslPkcs7($certPath, $privateKeyPath, getenv('KEY_PASS')),
'remoteSigner' => new OpenSslCliJwtSigner('/usr/bin/openssl'),
]
);
try {
$link = $provider->getAuthorizationUrl();
fwrite($file, $link."\n");
$_SESSION['oauth2.esia.state'] = $provider->getState();
fwrite($file, $_SESSION['oauth2.esia.state']."\n");
$result = [
'link' => $link,
'state' => $provider->getState(),
];
$result = json_encode($result);
} catch(\Exception $e) {
fwrite($file, $e->getMessage()."\n");
fwrite($file, $e->getTraceAsString()."\n");
$result = [
'error' => $e->getMessage(),
'openssl_error' => openssl_error_string(),
];
$result = json_encode($result);
} finally {
fwrite($file, "Код отработал!\n");
fclose($file);
echo $result;
}
return;
Данный скрипт успешно возвращает ссылку для авторизации на госуслугах.
- далее происходит редирект на ссылку и открывается тестовая страница госуслуг с формой ввода логина и пароля от учетной записи госуслуг.
- пользователь вводит логин и пароль и его редиректит на метод в контроллере для получения данных пользователя:
public function esiaResponse(Request $request)
{
$code = $request->get('code');
$state = $request->get('state');
$this->authentication->setState($state);
$accessToken = $this->authentication->getAccessToken($code);
$esiaUser = $this->authentication->getEsiaUser($accessToken);
}
метод getEsiaUser:
public function getEsiaUser(string $accessToken)
{
$client = new Client();
$response = $client->get($this->getUserUrl, [
'query' => [
'client_id' => $this->appId,
'redirect_uri' => $this->redirectUri,
'remote_url' => $this->remoteUrl,
'code' => $this->code,
'state' => $this->state,
]
]);
$body = json_decode($response->getBody(), true);
dd($body);
return $body['user'];
}
Здесь происходит обращение на контейнер с ГОСТ на файл finish.php:
$certPath = getenv('CERT_PATH');
$privateKeyPath = getenv('KEY_PATH');
$clientId = $_GET['client_id'];
$redirectUri = $_GET['redirect_uri'];
$remoteUrl = $_GET['remote_url'];
$code = $_GET['code'];
$state = $_GET['state'];
$provider = new EsiaProvider(
[
'clientId' => $clientId,
'redirectUri' => $redirectUri,
'defaultScopes' => ['birthdate', 'gender', 'email', 'mobile'],
'remoteUrl' => $remoteUrl,
'remoteCertificatePath' => EsiaProvider::RESOURCES.'esia.gost.prod.public.key',
],
[
'signer' => new OpensslPkcs7($certPath, $privateKeyPath, getenv('KEY_PASS')),
'remoteSigner' => new OpenSslCliJwtSigner('/usr/bin/openssl'),
]
);
try {
fwrite($file, $provider->getBaseAccessTokenUrl([])."\n");
$accessToken = $provider->getAccessToken('authorization_code', ['code' => $code]);
fwrite($file, $provider->getState()."\n");
fwrite($file, var_export($accessToken)."\n");
$esiaPersonData = $provider->getResourceOwner($accessToken);
$result = [
'user' => $esiaPersonData->toArray(),
'access_token' => $accessToken->__toString(),
];
$result = json_encode($result);
} catch(\Exception $e) {
fwrite($file, $e->getMessage()."\n");
fwrite($file, $e->getTraceAsString()."\n");
fwrite($file, json_encode(openssl_get_cert_locations())."\n");
$result = [
'error' => $e->getMessage(),
'openssl_error' => openssl_error_string(),
'openssl_cert_location' => openssl_get_cert_locations(),
];
$result = json_encode($result);
} finally {
fwrite($file, "Код отработал!\n");
fclose($file);
echo $result;
}
return;
Данный скрипт отрабатывает до строки $accessToken = $provider->getAccessToken('authorization_code', ['code' => $code]); и выбрасывает ошибку:
array:3 [▼
"error" => "cURL error 60: SSL certificate problem: unable to get local issuer certificate (see https://curl.haxx.se/libcurl/c/libcurl-errors.html)"
"openssl_error" => "error:140E0197:SSL routines:SSL_shutdown:shutdown while in init"
"openssl_cert_location" => array:8 [▼
"default_cert_file" => "/usr/lib/ssl/cert.pem"
"default_cert_file_env" => "SSL_CERT_FILE"
"default_cert_dir" => "/usr/lib/ssl/certs"
"default_cert_dir_env" => "SSL_CERT_DIR"
"default_private_dir" => "/usr/lib/ssl/private"
"default_default_cert_area" => "/usr/lib/ssl"
"ini_cafile" => "/etc/ca-certificates/cacert.crt"
"ini_capath" => ""
]
]
Все решения подобные https://stackoverflow.com/questions/24611640/curl-60-ssl-certificate-problem-unable-to-get-local-issuer-certificate, отключение верификации в cURL и Guzzle не решают мою проблему. Плюсом ко всему, был сделан HTTPS на локальном сервере - не помогло, размещение сборки на сервере с HTTPS так же не помогло.