Revision control
Copy as Markdown
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qandroidcacertificates_p.h"
#include <QtFFmpegMediaPluginImpl/private/qffmpegcertificateutils_p.h>
#include <QtCore/qdir.h>
#include <QtCore/qfile.h>
#include <QtCore/qjniarray.h>
#include <QtCore/qjniobject.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qsavefile.h>
#include <QtCore/qstandardpaths.h>
#include <jni.h>
#include <atomic>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
Q_DECLARE_JNI_CLASS(QtSslCertificates, "org/qtproject/qt/android/multimedia/qffmpeg/QtSslCertificates")
namespace QFFmpeg {
Q_STATIC_LOGGING_CATEGORY(qLCAndroidCaCertificates, "qt.multimedia.ffmpeg.android.caCertificates");
static QList<QByteArray> fetchAndroidSystemCertificates()
{
QList<QByteArray> derCertificates;
auto certificates =
QtJniTypes::QtSslCertificates::callStaticMethod<QJniArray<QJniArray<jbyte>>>(
"getCertificates");
if (!certificates.isValid())
return derCertificates;
derCertificates.reserve(certificates.size());
for (const auto &jCertificate : certificates)
derCertificates.append(jCertificate->toContainer());
return derCertificates;
}
QString androidFFmpegCaBundlePath()
{
static const QByteArray pemBundle = certificatesToPemBundle(fetchAndroidSystemCertificates());
if (pemBundle.isEmpty()) {
qCWarning(qLCAndroidCaCertificates) << "Could not obtain any system CA certificates";
return {};
}
const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
QString path = cacheDir + "/ffmpeg_ca_bundle.pem"_L1;
// A bundle left behind by a previous run may predate a system trust store update, so
// rewrite it once per process. After that, only rewrite it if it got evicted.
// Concurrent callers may both rewrite it, which is harmless: QSaveFile replaces the file
// atomically, so writers and readers never see a partially written file.
static std::atomic_bool writtenInThisProcess = false;
if (writtenInThisProcess.load(std::memory_order_relaxed) && QFile::exists(path))
return path;
QDir().mkpath(cacheDir);
QSaveFile file(path);
if (!file.open(QIODevice::WriteOnly) || file.write(pemBundle) != pemBundle.size()
|| !file.commit()) {
qCWarning(qLCAndroidCaCertificates) << "Failed to write CA bundle to" << path;
return {};
}
writtenInThisProcess.store(true, std::memory_order_relaxed);
return path;
}
} // namespace QFFmpeg
QT_END_NAMESPACE