php работа с сертификатами

Встраивание электронной подписи в системы с WEB-интерфейсом с помощью браузерного плагина и openssl

php работа с сертификатами. Смотреть фото php работа с сертификатами. Смотреть картинку php работа с сертификатами. Картинка про php работа с сертификатами. Фото php работа с сертификатами

Несколько лет назад нашей компанией был выпущен продукт Рутокен Плагин, который предназначен для встраивания электронной подписи в системы с web-интерфейсом. Основываясь на полученном опыте интеграции продукта в реальные проекты мне хочется отметить, что нередко разработчики для реализации серверной части предпочитают использовать поддерживающий российские криптоалгоритмы openssl.

В данной статье будет расписана типичная схема подобной интеграции, основанная на следующих сценариях использования плагина:

Данные сценарии предполагают клиент-серверное взаимодействие, написание клиентских скриптов на JavaScript и соответствующих им серверных вызовов openssl.

Подробности под катом.

Общие операции

Операции с устройством

Поиск подключенных устройств

Любой клиентский сценарий начинается с поиска подключенных к компьютеру USB-устройств Рутокен. В контексте данной статьи акцент делается на устройство Рутокен ЭЦП.

При этом возвращается список идентификаторов подключенных устройств. Идентификатор представляет собой число, связанное с номером слота, к которому подключено устройство. При повторном перечислении это число может отличаться для одного и того же устройства.

Рутокен Плагин определяет все подключенные к компьютеру USВ-устройства Рутокен ЭЦП, Рутокен PINPad, Рутокен WEB. Поэтому следующим шагом следует определить тип устройства.

Получение информации об устройстве

Для определения типа устройства следует использовать функцию getDeviceInfo с параметром TOKEN_INFO_DEVICE_TYPE. Значение этой константы содержится в объекте плагина.

Смена PIN-кода

Пример смены PIN-кода на устройстве:

Здесь первым параметром выступает старый PIN-код, а вторым новый PIN-код.

Работа с сертификатами

1. На токене могут храниться 3 категории сертификатов:

2. Для чтения сертификатов, хранящихся на устройстве, не требуется авторизация на устройство.

Пример чтения пользовательских сертификатов с устройства:

3. Сертификат можно экспортировать в PEM-формате:

Получится примерно такая строка:

4. Сертификат можно распарсить вызовом функции parseCertificate и получить из него DN Subject, DN Issuer, расширения, значение открытого ключа, подпись, серийный номер, срок действия и т.п.

5. Сертификат можно записать на устройство.

Пример записи сертификата на устройство как пользовательского:

6. Вызовом функции deleteCertificate можно удалить сертификат с токена.

Работа с ключевыми парами ГОСТ Р 34.10-2001

1. Для получения декрипторов ключевых пар, хранящихся на устройстве, требуется ввод PIN-кода. Следует понимать, что само значение закрытого ключ получено быть не может, так как ключ является неизвлекаемым.

Пример генерации ключевой пары ГОСТ Р 34.10-2001:

3. С помощью функции deleteKeyPair ключевая пара может быть удалена с токена.

Конфигурирование openssl

Openssl поддерживает российские криптоалгоритмы, начиная с версии 1.0. Для того, чтобы их использовать, в openssl требуется подгружать engine gost. В большинстве дистрибутивов openssl эта библиотека присутствует. Чтобы engine подгружалась, можно прописать ее в конфигурационном файле openssl:

Если конфигурационный файл openssl не расположен в стандартном месте, то путь к нему можно задать через переменную окружения OPENSSL_CONF.

Другим вариантом подгрузки engine gost является ее передача в параметрах командной строки утилиты openssl.

Если engine gost не расположена в стандартном месте, то через переменную окружения OPENSSL_ENGINES можно задать путь к директории, в которой openssl будет ее искать.

Для получения информации о том, успешен ли был вызов утилиты openssl или нет, с возможностью уточнения ошибки, требуется парсить stdout и stderror. В конце статьи приведена ссылка на PHP-скрипт, который использует данную утилиту.

Теперь перейдем к реализации законченных пользовательских сценариев.

Регистрация на портале

Сертификат выдается при регистрации в системе

Последовательность вызовов в клиентском скрипте будет следующей:

php работа с сертификатами. Смотреть фото php работа с сертификатами. Смотреть картинку php работа с сертификатами. Картинка про php работа с сертификатами. Фото php работа с сертификатами

Далее запрос отправляется на сервер, где на его основе выдается сертификат.
Для этого на сервере должен быть установлен и правильно сконфигурирован openssl версии от 1.0 и развернут функционал УЦ.

1. Генерация улюча УЦ:

После этого в файле ca.key будет создан закрытый ключ

2. Создание самоподписанного сертификата УЦ:

После ввода необходимой информации об издателе в файле ca.crt будет создан сертификат УЦ.

Полученный от клиента запрос сохраняем в файл user.csr и выдаем на его основе сертификат (без модификации данных из запроса):

После этого в файле user.crt создается сертификат пользователя в PEM формате. Его следует отправить на клиент.
Дальнейшая последовательность вызовов на клиенте:

php работа с сертификатами. Смотреть фото php работа с сертификатами. Смотреть картинку php работа с сертификатами. Картинка про php работа с сертификатами. Фото php работа с сертификатами

Сертификат уже имеется на токене, выдан внешним УЦ

Ключевая пара при этом должна быть создана в формате, совместимом с библиотекой rtPKCS11ECP для Рутокен ЭЦП.

Последовательность вызовов на клиенте:
php работа с сертификатами. Смотреть фото php работа с сертификатами. Смотреть картинку php работа с сертификатами. Картинка про php работа с сертификатами. Фото php работа с сертификатами

Подпись получается в base64-формате. При проверке ее на сервере с помощью openssl подпись следует обрамить заголовками, чтобы сделать из нее PEM. Выглядеть подобная подпись будет примерно так:

Проверка подписи на сервере:

Здесь sign.cms — файл, в котором находится подпись, ca.crt — файл с корневыми сертификатами, до одного из которых должна выстроиться цепочка, data.file — файл, в который будет сохранены подписанные данные, user.crt — файл, в который будет сохранен пользовательский сертификат. Именно из data.file нужно извлечь данные отсоединить последние 32 символа и сравнить salt.

Если на сервере нужно получить информацию из сертификата, то парсить его можно так:

Показать содержимое сертификата в текстовом представлении:

Показать серийный номер сертификата:

Показать DN субъекта (subject):

Показать DN издателя:

Показать почтовый адрес субъекта:

Показать время начала действия сертификата:

Показать время окончания действия сертификата:

Строгая аутентификация на портале

Электронная подпись данных и/или файлов в формате CMS

Проверка подписи на сервере описана выше.

Шифрование/расшифрование данных и/или файлов в формате CMS

Шифрование данных на клиенте для сервера

Для того, чтобы обеспечить конфиденциальность обмена данными между клиентом и сервером в плагине предусмотрено шифрование/расшифрование данных. Данные шифруются в формате CMS. Для того, чтобы зашифровать данные в формате CMS, требуется сертификат открытого ключа «адресата». При этом расшифровать такое сообщение сможет только владелец закрытого ключа. При шифровании данных для сервера рекомендуется хранить сертификат сервера на Рутокен ЭЦП. Этот сертификат может быть записан на устройство при регистрации пользователя на портале. Для этого следует использовать функцию importCertificate, при этом в качестве параметра category следует передать CERT_CATEGORY_OTHER. Для использования в функции cmsEncrypt нужно получить тело сертификата по его дескриптору с помощью функции getCertificate. При этом дескриптор является уникальным и неизменным и может быть сохранен в учетной записи пользователя на сервере при импорте сертификата сервера. Для того, чтобы использовалось аппаратное шифрование по ГОСТ 28147-89, требуется установить опцию useHardwareEncryption в true. В противном случае будет использована быстрая программная реализация ГОСТ 28147-89.

Последовательность вызовов приведена на картинке:

php работа с сертификатами. Смотреть фото php работа с сертификатами. Смотреть картинку php работа с сертификатами. Картинка про php работа с сертификатами. Фото php работа с сертификатами

Шифрование данных на клиенте:

Расшифрование данных на сервере, перед расшифрованием сообщение нужно обрамить PEM-заголовками «——BEGIN PKCS7——» и «——END PKCS7——«:

recipient.crt — сертификат того, для кого зашифровано сообщение, recipient.key — ключ того, для кого зашифровано сообщение.

Расшифрование данных, полученных с сервера, на клиенте

Для расшифрования данных, полученных с сервера, предназначена функция cmsDecrypt. Так как сервер шифрует для клиента, используя его сертификат, то в качестве keyId должен быть передан дескриптор закрытого ключа клиента, соответствующий открытому ключу в сертификате. Этот дескриптор является уникальным и неизменным и потому может быть сохранен в учетной записи пользователя на сервере. Кроме того, дескриптор ключа пользователя может быть получен явным образом, путем вызова функции getKeyByCertificate.

Шифрование данных на сервере для клиента:

Расшифрование данных на клиенте:

Полезные ссылки

Данные ссылки могут быть полезны разработчикам инфосистем с поддержкой ЭЦП на базе Рутокен Плагин и openssl:

Источник

Взаимодействие php-soap на linux с авторизацией по сертификатам с использованием алгоритмов ГОСТ

С криптографией я сталкивался ранее, приходилось разворачивать удостоверяющий центр на КриптоПро в свое время, так что общие представления о том что такое закрытые и открытые ключи и сертификаты у меня имелось, но вот о том как все это работает в Linux представления особого не было.
Встала задача обеспечить взаимодействие со службами РосМинздрава, а именно — с федеральным регистром медработников по протоколу SOAP. Со стороны клиента система на CentOS и работающими службами на PHP, со стороны сервера — SOAP-сервис с авторизацией по сертификатам с использованием ГОСТ алгоритмов. В наличии была флешка с закрытым ключом, сформированным удостоверяющим центром РосМинздрава и сертификат этого ключа.
После анализа ситуации по использованию ГОСТ алгоритмов шифрования в мире Linux выяснено что за последние годы здесь есть хорошее движение вперед, но все таки не совсем все хорошо. Итак, для того чтобы заставить расширение php-soap прозрачно понимать алгоритмы ГОСТ, а также использовать сертификаты и ключи выданные РосМинздравом нужно сделать следующее:

1. Обновить в дистрибутиве библиотеку OpenSSL до версии не ниже 1.0.1с и настроить поддержку ГОСТ.
2. Преобразовать выданный ключ и сертификат в формат, понятный OpenSSL. Проверить работу OpenSSL.
3. Подправить расширение OpenSSL в PHP и перекомпилировать сам PHP. Протестировать работу SOAP на PHP.

Итак, приступим. Данные дистрибутива, установленного php и библиотеки openssl:

Обновление библиотеки OpenSSL

Метод собирать «из исходников» и засорять систему — не наш метод. Поэтому лучший вариант — найти готовый пакет, и я его нашел в репозитории axivo:

Проверяем установленную версию OpenSSL:

и в самый конец файла:

После внесенных изменений проверяем, видит ли OpenSSL алгоритмы ГОСТ:

Преобразуем выданный ключ и сертификат в формат, понятный OpenSSL

Для этого нам понадобится Windows машина с установленным CryptoPRO CSP 3.6. Я его слил с сайта разработчика (там дается демонстрационный режим режим на 3 месяца).
Первое что делаем — экспортируем ключевой контейнер в реестр средствами КриптоПро. Для этого открываем КриптоПро из панели управления Windows. Вставляем наш ключевой носитель в компьютер. Для обычной флешки убеждаемся, что у нас среди считывателей на вкладке «Оборудование» есть «Все съемные диски» и «Реестр». Если у вас не обычная флешка а что-то типа РуТокен либо Etoken либо другой носитель — необходимо наличие его драйверов и библиотеки для использования в КриптоПро (Его должно быть видно в разделе «Настроить Считыватели» на той же вкладке).

Далее переходим на вкладку «Сервис» и нажимаем «Копировать», нажимаем «Обзор» и выбираем среди списка свой ключевой контейнер. Если его там вдруг не находим — то возвращаемся к предыдущему параграфу и все проверяем. Далее вводим осмысленное имя нового ключевого контейнера, нажимаем «Готово» и в качестве носителя в появившемся окне выбираем «Реестр». Новый пароль на контейнер не ставим.
Теперь нам нужно установить сертификат в новый контейнер. На вкладке «Сервис» в КриптоПро нажимаем кнопку «Установить личный Сертификат», указываем файл с нашим сертификатом, нажимаем «Далее», выбираем только что созданный нами контейнер и не забываем галочку «Установить сертификат в контейнер».
Для того чтобы экспортировать данный сертификат в формате PKSC#12 вместе с закрытым ключом — нам понадобится сторонняя утилита. Которую можно либо купить здесь либо найти на просторах интернета по имени p12fromgostcsp. Запускаем данную утилиту, выбираем наш сертификат, пароль оставляем пустым и сохраняем его в файл mycert.p12.
Копируем полученный сертификат на linux-машину. Проверяем, что в сертификате у нас есть оба ключа — закрытый и открытый:

На запрос пароля просто нажимаем Enter. И смотрим наличие строк BEGIN CERTIFICATE и BEGIN PRIVATE KEY. Если строки есть то все в порядке. Преобразуем полученный сертификат в формат PEM:

Если вам нужна не только авторизация по клиентскому сертификату, но и также проверка валидности самого сервера — вам понадобится корневой сертификат удостоверяющего центра. Его можно просто через Windows открыть и сохранить в формате DER в кодировке X.509 (Со вкладки «Состав» при просмотре сертификата), так как закрытого ключа он не содержит. Затем преобразовать его в PEM формат через OpenSSL.

Где cacert.cer — имя исходного файла с корневым сертификатом. Проверить коннект с сервером с использованием сертификатов можно через OpenSSL командой:

В итоге, если все сделано верно, у вас должен получится вот такой вывод в конце:

Правка и перекомпиляция PHP

Основная проблема заключается в том, что для того чтобы OpenSSL использовала файл конфигурации по умолчанию (именно там у нас прописаны настройки для алгоритмов ГОСТ) перед ее использованием необходимо вызвать функцию OPENSSL_config(NULL). В расширении PHP OpenSSL этого не сделано, поэтому модуль PHP-SOAP при использовании SSL-соединения не видит алгоритмов ГОСТ. Кстати, тоже самое касается и других библиотек, например curl. Ее тоже нужно патчить если вы собираетесь с ней работать.
Итак приступим. Для того чтобы нам поправить OpenSSL необходимо перекомпилировать весь PHP, так как в CentOS расширение OpenSSL сделано не модулем.
Подготавливаем среду для сборки пакетов:

Качаем исходники PHP и устанавливаем их:

Переходим в папку SOURCES и извлекаем php, делаем копию папки с иходниками:

В результате получится файл со следующим содержимым:

Теперь нужно заставить сборщик использовать наш патч. Правила сборки описаны в файле SPEC/php.spec. Открываем его и после строки описания патчей (у меня это строка начинающаяся на Patch230) вставляем строку с описанием нового патча:

Также прописываем вызов данного патча перед сборкой, для этого ищем строки начинающиеся на %patch и в конце них прописываем

Сохраняем файл. Перед сборкой пакета ставим все зависимости для сборки:

Далее приступаем к сборке, из папки rpmbuld запускаем команду:

Сборка занимает продолжительное время, и в случае если все прошло хорошо, в папке RPMS/ <Ваша_архитектура>появятся готовые rpm_пакеты.
Для архитектуры x86_64:

Теперь можем установить все это «добро» поверх уже установленного php с заменой:

Далее проверяем работу php-soap. Для примера можно использовать такой код (пример для сервиса «Федеральный регистр медработников»):

Если все прошло успешно никаких ошибок не возникнет и PHP-SOAP без проблем заработает.
Далее если необходимо можно также подправить библиотеку curl, сделать проверку сертификата сервера и вообще все операции которые допустимы для SSL-соединений вплоть до поднятия своего веб сервера с авторизацией по сертификатам с алгоритмами ГОСТ.

Источник

openssl_csr_new

(PHP 4 >= 4.2.0, PHP 5, PHP 7, PHP 8)

openssl_csr_new — Генерирует CSR

Описание

Список параметров

Уникальное имя (Distinguished Name) или поля субъекта для использования в сертификате.

Параметр private_key должен быть задан закрытым ключом ранее сгенерированным функцией openssl_pkey_new() (или полученный с помощью любой другой функции семейства openssl_pkey). Соответствующая открытая часть ключа будет использована для подписания CSR.

Возвращаемые значения

Возвращает CSR или false в случае возникновения ошибки.

Список изменений

Примеры

Пример #1 Создание самоподписанного сертификата

Пример #2 Создание самоподписанного ECC сертификата (начиная с PHP 7.1.0)

Смотрите также

User Contributed Notes 10 notes

Not sure whether the «bug» (undocumented behavior) I encountered is common to other people, but this comment might save hours of painful debug:
If you can’t generate a new private key using openssl_pkey_new() or openssl_csr_new(), your script hangs during the call of these functions and in case you specified a «private_key_bits» parameter, ensure that you cast the variable to an int. Took me ages to notice that.

When in doubt, read the source code to PHP!

$configargs is fairly opaque as to what is going on behind the scenes. That is, until you actually look at php_openssl_parse_config() in ‘/ext/openssl/openssl.c’:

SET_OPTIONAL_STRING_ARG(«digest_alg», req->digest_name,
CONF_get_string(req->req_config, req->section_name, «default_md»));
SET_OPTIONAL_STRING_ARG(«x509_extensions», req->extensions_section,
CONF_get_string(req->req_config, req->section_name, «x509_extensions»));
SET_OPTIONAL_STRING_ARG(«req_extensions», req->request_extensions_section,
CONF_get_string(req->req_config, req->section_name, «req_extensions»));
SET_OPTIONAL_LONG_ARG(«private_key_bits», req->priv_key_bits,
CONF_get_number(req->req_config, req->section_name, «default_bits»));

SET_OPTIONAL_LONG_ARG(«private_key_type», req->priv_key_type, OPENSSL_KEYTYPE_DEFAULT);

Here we can see that SET_OPTIONAL_STRING_ARG() is called for most inputs but for ‘private_key_bits’ SET_OPTIONAL_LONG_ARG() is called. Both calls are C macros that expand to code that enforces the expected input type. The generated code ignores the input without warning/notice if an unexpected type is used and just uses the default from the configuration file. This is why using a string with ‘private_key_bits’ will result in unexpected behavior.

Further inspection of the earlier initialization in the same function:

SET_OPTIONAL_STRING_ARG(«config», req->config_filename, default_ssl_conf_filename);
SET_OPTIONAL_STRING_ARG(«config_section_name», req->section_name, «req»);
req->global_config = CONF_load(NULL, default_ssl_conf_filename, NULL);
req->req_config = CONF_load(NULL, req->config_filename, NULL);

if (req->req_config == NULL) <
return FAILURE;
>

And elsewhere in another function:

/* default to ‘openssl.cnf’ if no environment variable is set */
if (config_filename == NULL) <
snprintf(default_ssl_conf_filename, sizeof(default_ssl_conf_filename), «%s/%s»,
X509_get_default_cert_area(),
«openssl.cnf»);
> else <
strlcpy(default_ssl_conf_filename, config_filename, sizeof(default_ssl_conf_filename));
>

All of that goes to show that looking at the PHP source code is the only real way to figure out what is actually happening. Doing so saves time and effort.

That’s my example of code to sign a «child» certificate :

$CAcrt = «file://ca.crt»;
$CAkey = array(«file://ca.key», «myPassWord»);

Then if you want to add some more options, you can edit the «/etc/ssl/openssl.cnf» ssl config’ file (debian path), and add these after the [ v3_req ] tag.

There appears to be no openssl_csr_free function.

If it’s in the source, one might be able to just call it.

If it’s not in the source, it probably should be.

If you get the error:

error:0D11A086:asn1 encoding routines:ASN1_mbstring_copy:string too short

If you have one value (like «organizationalUnitName» = «») set to an empty string, it will throw the above error.

Источник

Я — сертифицированный PHP-специалист

Да, наверное, возможность применения данного выражения — греет кому-то душу, но я немного о другом.

Разрешите поделиться опытом прохождения сертификации по PHP 5.5 от компании Zend Technologies.

Путь от «да, я хочу получить статус ZCE» до покупки ваучера

4 года — ровно столько времени потребовалось от простого «Да, не плохо было бы получить сертификат» до «Девушка, смотрите, а я сдал»

Если у вас появится такая мысль, то открывая в очередной раз Америку, скажу — вам помогут: правильно и ясно поставленная цель; четко разграниченные сроки; план действий.

Сроки

Следующим пунктом была запись о сроках: два месяца + погрешность в один месяц. По факту, погрешность растянулась до двух месяцев, тому есть свои причины, не связанные с работой или ленью. Запомните, что «Slow progress is better than no progress». Данное выражение закрепилось в моей голове по ходу занятий на перекладине, или турнике — кому как удобно. Ведь часто важно просто идти, и не беда, что временами медленно.

План действий

Как отметил выше — обязательным пунктом выделен процесс штудирования мануала, — ваш верный друг и товарищ.
Из видео-пособий просмотрел лишь об SPL, о некоторых особенностях SAX-XML и о потоках, — streams.

Основные темы для сертификации предоставлены на странице List of Exam Topics

Отходить от них и изучать что-то дополнительно — не принципиально, хотя уточню, что проштудировал отдельно спецификацию XML, XPath и Regex.

Прелюдией к 15+15 дней было чтение PHP Certification Study Guide.

Некоторые обстоятельства способствовали к тому, чтобы сузить кошелек и довольствоваться версией 5.3 — не жалею, но читать этот гайд нужно до мануала т.к. понятное дело, что есть разница между первой и последней версиями.

В первых главах гайда вам будет предоставлена информация о процессе самого тестирования, количестве вопросов, времени в которое нужно уложиться. Выписать отдельным пунктом советую наиболее акцентированные главы, за которые как предполагается — дают самые высокие баллы.

Вот, как это выглядит:

php работа с сертификатами. Смотреть фото php работа с сертификатами. Смотреть картинку php работа с сертификатами. Картинка про php работа с сертификатами. Фото php работа с сертификатами

В процессе подготовки, посчитал удобным использовать сервис Trello и перенес все темы и главы в колонку «TODO».
А вот и результат — все дружно выстроились в колонке «Done»:

php работа с сертификатами. Смотреть фото php работа с сертификатами. Смотреть картинку php работа с сертификатами. Картинка про php работа с сертификатами. Фото php работа с сертификатами

Отдельный виртуальный хост для конспектирования и практики:

php работа с сертификатами. Смотреть фото php работа с сертификатами. Смотреть картинку php работа с сертификатами. Картинка про php работа с сертификатами. Фото php работа с сертификатами

Прохождение пробных тестов

Zend Technologies предоставляют возможность купить пробные тесты, которые стоят дороже самого ваучера, поэтому данный вспомогательный ресурс будет вполне достаточен.

Всего 171 вопросов, 35 страниц. Можете пройти все сразу, можете разделить на ломтики из 10/35 страниц.

Ваша задача: пройти тест, выделить все ваши слабые места, повторить их и снова пройти тест. 90-99 из 100% — и вы можете переходить к следующему шагу — к покупке ваучера.

Как я поступил?

Как полагается, закономерным продолжением должна быть покупка ваучера и прохождение теста, но — «эффект Comic Sans»: «перепроверю знания; повторю некоторые главы».

Преодолеть этот порог, как ни странно, помог Гай Юлий Цезарь и выражение «Перейти Рубикон». Я бросил жребий, купил ваучер и перешел свой Рубикон. Нет пути назад, если вы не явитесь в указанное время в тестовый центр, то ваш ваучер сгорает.

За день до тестов вам нужно хорошенько отдохнуть, а на самом тесте — не нервничать. Отсутствие отдыха и напряженность — будут мешать вам в самом процессе.

Хотя, насчет «не нервничать» — я приукрасил. «Не переживай, ты уже выполнил все, то что будет — это будет» — говорил себе; последние 10 минут боролся именно с этим состоянием и смог уговорить внутренне я лишь на 89 минуте.

2 секунды, 1 секунда, ноль — и вот это окошко и облегчение в душе.

php работа с сертификатами. Смотреть фото php работа с сертификатами. Смотреть картинку php работа с сертификатами. Картинка про php работа с сертификатами. Фото php работа с сертификатами

Намеренно не стал уточнять о процессе покупки ваучера, о запутанных вопросах и других темах, которые раскрыты предыдущими сертифицированными специалистами Хабра.

Профит

В дополнении к PHP: уверенные академические знания в Regex, XML и сопутствующих технологиях, в потоках.
Стал более внимательно читать мануалы и справочники по другим продуктам и языкам программирования. Привет Python!

Цель достигнута. Десятый по счету ZCE у себя в стране и второй по версии 5.5.

Верю и надеюсь, что получиться сподвигнуть некоторых из вас решиться и получить свой сертификат. Не бойтесь бросать вызовы самим себе. Сертификат не сделает из вас самого крутого специалиста — это достигается эмпирическим путем, путем проб и ошибок, и других различных факторов, но брошенный и выполненный вызов — дает массу положительных эмоций.

Буду весьма рад ответить на ваши вопросы. Спасибо.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *