Данная статья является логическим продолжением статьи про генерацию сертификата. В ней мы рассмотрим, как мы можем провалидировать сертификат используя OpenSSL и C++.
В OpenSSL, для работы с сертификатами используется понятие storage. Давайте создадим его используя X509_STORE_new();
std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)> store(
X509_STORE_new(), &::X509_STORE_free);
Далее, нам нужно "заполнить" наше хранилище сертификатами, которыми мы планируем проверять наш целевой сертификат. Если наш сертификат подписан сертификатом, который уже находится в trusted storage, то нам достаточно вызвать лишь одну функцию - X509_STORE_load_path(). В нее необходимо передать указатель на наш сторадж, а также полный путь к сертификатам. Для линукса такой путь может быть: /etc/ssl/certs.
bool addCAPath(X509_STORE* store, const char* path) {
return X509_STORE_load_path(store, path) == 1;
}
Также, в OpenSSL есть возможность загрузить файл с несколькими сертификатами, используя функцию X509_STORE_load_file(). В нее также передаем указатель на сторадж, а также имя файла. Например, /etc/ssl/certs/ca-certificates.crt.
bool addCABundle(X509_STORE* store, const char* path) {
X509_STORE_load_file(store, path) == 1;
}
Если же, мы хотим добавить в сторадж наш собственный сертификат, которым мы собираемся валидировать целевой, то мы можем добавить его отдельно, воспользовавшись X509_STORE_add_cert(). В него передаем указатель на сторадж, а также сам сертификат, который мы хотим добавить.
bool addCert(X509_STORE* store, X509* cert) {
return X509_STORE_add_cert(store, cert) == 1;
}
Далее, мы можем начинать процесс валидации сертификата. Для начала, нам необходимо сконструировать контекcт X509_CTX. К слову сказать, почти все новое АПИ OpenSSL 3.0.0 строится вокруг различных контекстов. Чтобы не мучиться с управлением ресурсами, создадим нашему контексту специальный делитор, состоящий из вызова двух функций: очистить контекст и освободить память под контекст.
auto storeContextDeleter = [](X509_STORE_CTX* ctx) {
X509_STORE_CTX_cleanup(ctx);
X509_STORE_CTX_free(ctx);
};
std::unique_ptr<X509_STORE_CTX, decltype(storeContextDeleter)>
storeCtx(X509_STORE_CTX_new(), storeContextDeleter);
if (storeCtx == nullptr) {
std::cerr << "Failed X509_STORE_CTX_new" << std::endl;
return -1;
}
Итак, мы создали контекст, далее, мы можем выставить колбек, который будет вызываться OpenSSL после валидации сертификата. Вы спросите - зачем это нужно, а нужно это затем, что в этом колбеке мы можем подавить различные ошибки OpenSSL, чтобы валидация считалась успешной. Например, если OpenSSL вернул нам в этом колбеке ошибку - X509_V_ERR_CERT_HAS_EXPIRED, что значит, что сертификат уже "протух", но мы по своим причинам хотим разрешить такое непотребство, то мы можем на нее просто вернуть OK.
static int verifyCallback(int ok, X509_STORE_CTX *ctx) {
const int err = X509_STORE_CTX_get_error(ctx);
if (err != 0) {
std::cerr << "Failed to verify cert " << err << std::endl;
}
return ok;
}
///
X509_STORE_set_verify_cb_func(store.get(), verifyCallback);
Далее, инициализируем наш контекст и вызываем функцию валидации:
if (X509_STORE_CTX_init(storeCtx.get(), store.get(), cert.get(), nullptr) == 0) {
std::cerr << "Failed X509_STORE_CTX_init" << std::endl;
return -1;
}
if (X509_verify_cert(storeCtx.get()) != 1) {
std::cerr << "Failed X509_verify_cert" << std::endl;
return -1;
}
Больше тут рассказывать и нечего. Буду рад прочитать ваши комментарии. Все примеры, как и раньше, загружены на мой гитхаб. В следующей статье будет описана работы с ключами, а также пачка полезных функций для чтения и записи ключей, и сертификатов.