diff options
author | Denis Shienkov <denis.shienkov@gmail.com> | 2021-03-29 18:15:17 +0300 |
---|---|---|
committer | Denis Shienkov <denis.shienkov@gmail.com> | 2021-04-23 14:55:38 +0000 |
commit | 2f6eecdc96fcd693cecef8011d8f9500c7872fc7 (patch) | |
tree | 8bed88e65e4b3fd8c415c3beba3afd226c7d6481 /tests/auto | |
parent | 2f34e637828f7e519a25a498bd5aa4e8f955217d (diff) | |
download | qbs-2f6eecdc96fcd693cecef8011d8f9500c7872fc7.tar.gz |
codesign: Long live `signtool` signing on Windows
Change-Id: I320cd1a1f3d8a1eed11d1c70007214f19a109b6e
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
Reviewed-by: Ivan Komissarov <ABBAPOH@gmail.com>
Diffstat (limited to 'tests/auto')
-rw-r--r-- | tests/auto/auto.qbs | 3 | ||||
-rw-r--r-- | tests/auto/blackbox/CMakeLists.txt | 9 | ||||
-rw-r--r-- | tests/auto/blackbox/blackbox-windows.pro | 18 | ||||
-rw-r--r-- | tests/auto/blackbox/blackbox-windows.qbs | 21 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata-windows/codesign/app.cpp | 1 | ||||
-rw-r--r-- | tests/auto/blackbox/testdata-windows/codesign/codesign.qbs | 37 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackboxwindows.cpp | 174 | ||||
-rw-r--r-- | tests/auto/blackbox/tst_blackboxwindows.h | 51 |
8 files changed, 313 insertions, 1 deletions
diff --git a/tests/auto/auto.qbs b/tests/auto/auto.qbs index 8dd301f68..0d87af9fe 100644 --- a/tests/auto/auto.qbs +++ b/tests/auto/auto.qbs @@ -4,7 +4,6 @@ Project { name: "Autotests" references: [ "api/api.qbs", - "blackbox/blackbox.qbs", "blackbox/blackbox-android.qbs", "blackbox/blackbox-apple.qbs", "blackbox/blackbox-baremetal.qbs", @@ -13,6 +12,8 @@ Project { "blackbox/blackbox-java.qbs", "blackbox/blackbox-joblimits.qbs", "blackbox/blackbox-qt.qbs", + "blackbox/blackbox-windows.qbs", + "blackbox/blackbox.qbs", "buildgraph/buildgraph.qbs", "cmdlineparser/cmdlineparser.qbs", "language/language.qbs", diff --git a/tests/auto/blackbox/CMakeLists.txt b/tests/auto/blackbox/CMakeLists.txt index 88eed4b42..0bf79a433 100644 --- a/tests/auto/blackbox/CMakeLists.txt +++ b/tests/auto/blackbox/CMakeLists.txt @@ -70,3 +70,12 @@ add_qbs_test(blackbox-qt tst_blackboxqt.cpp tst_blackboxqt.h ) + +add_qbs_test(blackbox-windows + SOURCES + ../shared.h + tst_blackboxbase.cpp + tst_blackboxbase.h + tst_blackboxwindows.cpp + tst_blackboxwindows.h + ) diff --git a/tests/auto/blackbox/blackbox-windows.pro b/tests/auto/blackbox/blackbox-windows.pro new file mode 100644 index 000000000..a9e8fdbd2 --- /dev/null +++ b/tests/auto/blackbox/blackbox-windows.pro @@ -0,0 +1,18 @@ +TARGET = tst_blackbox-windows + +HEADERS = tst_blackboxwindows.h tst_blackboxbase.h +SOURCES = tst_blackboxwindows.cpp tst_blackboxbase.cpp +OBJECTS_DIR = windows +MOC_DIR = $${OBJECTS_DIR}-moc + +include(../auto.pri) + +DATA_DIRS = testdata-windows ../find + +for(data_dir, DATA_DIRS) { + files = $$files($$PWD/$$data_dir/*, true) + win32:files ~= s|\\\\|/|g + for(file, files):!exists($$file/*):FILES += $$file +} + +OTHER_FILES += $$FILES diff --git a/tests/auto/blackbox/blackbox-windows.qbs b/tests/auto/blackbox/blackbox-windows.qbs new file mode 100644 index 000000000..e32421e3b --- /dev/null +++ b/tests/auto/blackbox/blackbox-windows.qbs @@ -0,0 +1,21 @@ +import qbs.Utilities + +QbsAutotest { + testName: "blackbox-windows" + Depends { name: "qbs_app" } + Depends { name: "qbs-setup-toolchains" } + Group { + name: "testdata" + prefix: "testdata-windows/" + files: ["**/*"] + fileTags: [] + } + files: [ + "../shared.h", + "tst_blackboxbase.cpp", + "tst_blackboxbase.h", + "tst_blackboxwindows.cpp", + "tst_blackboxwindows.h", + ] + cpp.defines: base.concat(["SRCDIR=" + Utilities.cStringQuote(path)]) +} diff --git a/tests/auto/blackbox/testdata-windows/codesign/app.cpp b/tests/auto/blackbox/testdata-windows/codesign/app.cpp new file mode 100644 index 000000000..76e819701 --- /dev/null +++ b/tests/auto/blackbox/testdata-windows/codesign/app.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/tests/auto/blackbox/testdata-windows/codesign/codesign.qbs b/tests/auto/blackbox/testdata-windows/codesign/codesign.qbs new file mode 100644 index 000000000..2b48c67ff --- /dev/null +++ b/tests/auto/blackbox/testdata-windows/codesign/codesign.qbs @@ -0,0 +1,37 @@ +Project { + name: "p" + + property bool enableSigning: true + property string hashAlgorithm + property string subjectName + property string signingTimestamp + + CppApplication { + name: "A" + files: "app.cpp" + codesign.enableCodeSigning: project.enableSigning + codesign.hashAlgorithm: project.hashAlgorithm + codesign.subjectName: project.subjectName + codesign.signingTimestamp: project.signingTimestamp + install: true + installDir: "" + property bool dummy: { + console.info("signtool path: %%" + codesign.codesignPath + "%%"); + } + } + + DynamicLibrary { + Depends { name: "cpp" } + name: "B" + files: "app.cpp" + codesign.enableCodeSigning: project.enableSigning + codesign.hashAlgorithm: project.hashAlgorithm + codesign.subjectName: project.subjectName + codesign.signingTimestamp: project.signingTimestamp + install: true + installDir: "" + property bool dummy: { + console.info("signtool path: %%" + codesign.codesignPath + "%%"); + } + } +} diff --git a/tests/auto/blackbox/tst_blackboxwindows.cpp b/tests/auto/blackbox/tst_blackboxwindows.cpp new file mode 100644 index 000000000..0c82754fb --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxwindows.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2021 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms and +** conditions see http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "tst_blackboxwindows.h" + +#include "../shared.h" + +#include <tools/hostosinfo.h> + +#include <QtCore/qdir.h> +#include <QtCore/qregularexpression.h> + +using qbs::Internal::HostOsInfo; + +struct SigntoolInfo +{ + enum class CodeSignResult { Failed = 0, Signed, Unsigned }; + CodeSignResult result = CodeSignResult::Failed; + bool timestamped = false; + QString hashAlgorithm; + QString subjectName; +}; + +Q_DECLARE_METATYPE(SigntoolInfo::CodeSignResult) + +static SigntoolInfo extractSigntoolInfo(const QString &signtoolPath, const QString &appPath) +{ + QProcess signtool; + signtool.start(signtoolPath, { QStringLiteral("verify"), QStringLiteral("/v"), appPath }); + if (!signtool.waitForStarted() || !signtool.waitForFinished()) + return {}; + const auto output = signtool.readAllStandardError(); + SigntoolInfo signtoolInfo; + if (output.contains("No signature found")) { + signtoolInfo.result = SigntoolInfo::CodeSignResult::Unsigned; + } else { + signtoolInfo.result = SigntoolInfo::CodeSignResult::Signed; + const auto output = signtool.readAllStandardOutput(); + const auto lines = output.split('\n'); + for (const auto &line: lines) { + { + const QRegularExpression re("^Hash of file \\((.+)\\):.+$"); + const QRegularExpressionMatch match = re.match(line); + if (match.hasMatch()) { + signtoolInfo.hashAlgorithm = match.captured(1).toLocal8Bit(); + continue; + } + } + { + const QRegularExpression re("Issued to: (.+)"); + const QRegularExpressionMatch match = re.match(line); + if (match.hasMatch()) { + signtoolInfo.subjectName = match.captured(1).toLocal8Bit().trimmed(); + continue; + } + } + if (line.startsWith("The signature is timestamped:")) { + signtoolInfo.timestamped = true; + break; + } else if (line.startsWith("File is not timestamped.")) { + break; + } + } + } + return signtoolInfo; +} + +static QString extractSigntoolPath(const QByteArray &output) +{ + const QRegularExpression re("%%(.+)%%"); + QRegularExpressionMatchIterator it = re.globalMatch(output); + if (!it.hasNext()) + return {}; + const QRegularExpressionMatch match = it.next(); + return match.captured(1).toUtf8(); +} + +TestBlackboxWindows::TestBlackboxWindows() + : TestBlackboxBase (SRCDIR "/testdata-windows", "blackbox-windows") +{ +} + +void TestBlackboxWindows::initTestCase() +{ + if (!HostOsInfo::isWindowsHost()) { + QSKIP("only applies on Windows"); + return; + } + + TestBlackboxBase::initTestCase(); +} + +void TestBlackboxWindows::standaloneCodesign() +{ + QFETCH(SigntoolInfo::CodeSignResult, result); + QFETCH(QString, hashAlgorithm); + QFETCH(QString, subjectName); + QFETCH(QString, signingTimestamp); + + QDir::setCurrent(testDataDir + "/codesign"); + QbsRunParameters params(QStringList{"qbs.installPrefix:''"}); + params.arguments << QStringLiteral("project.enableSigning:%1").arg( + (result == SigntoolInfo::CodeSignResult::Signed) ? "true" : "false") + << QStringLiteral("project.hashAlgorithm:%1").arg(hashAlgorithm) + << QStringLiteral("project.subjectName:%1").arg(subjectName) + << QStringLiteral("project.signingTimestamp:%1").arg(signingTimestamp); + + rmDirR(relativeBuildDir()); + QCOMPARE(runQbs(params), 0); + + if (!m_qbsStdout.contains("signtool path:")) + QFAIL("No current signtool path pattern exists"); + + const auto signtoolPath = extractSigntoolPath(m_qbsStdout); + QVERIFY(QFileInfo(signtoolPath).exists()); + + const QStringList outputBinaries = {"A.exe", "B.dll"}; + for (const auto &outputBinary : outputBinaries) { + const auto outputBinaryPath = defaultInstallRoot + "/" + outputBinary; + QVERIFY(QFileInfo(outputBinaryPath).exists()); + + const SigntoolInfo signtoolInfo = extractSigntoolInfo(signtoolPath, outputBinaryPath); + QVERIFY(signtoolInfo.result != SigntoolInfo::CodeSignResult::Failed); + QCOMPARE(signtoolInfo.result, result); + QCOMPARE(signtoolInfo.hashAlgorithm, hashAlgorithm); + QCOMPARE(signtoolInfo.subjectName, subjectName); + QCOMPARE(signtoolInfo.timestamped, !signingTimestamp.isEmpty()); + } +} + +void TestBlackboxWindows::standaloneCodesign_data() +{ + QTest::addColumn<SigntoolInfo::CodeSignResult>("result"); + QTest::addColumn<QString>("hashAlgorithm"); + QTest::addColumn<QString>("subjectName"); + QTest::addColumn<QString>("signingTimestamp"); + + QTest::newRow("standalone, unsigned") + << SigntoolInfo::CodeSignResult::Unsigned << "" << "" << ""; + QTest::newRow("standalone, signed, sha1, qbs@community.test, no timestamp") + << SigntoolInfo::CodeSignResult::Signed << "sha1" << "qbs@community.test" << ""; + QTest::newRow("standalone, signed, sha256, qbs@community.test, RFC3061 timestamp") + << SigntoolInfo::CodeSignResult::Signed << "sha256" << "qbs@community.test" + << "http://timestamp.digicert.com"; +} + +QTEST_MAIN(TestBlackboxWindows) diff --git a/tests/auto/blackbox/tst_blackboxwindows.h b/tests/auto/blackbox/tst_blackboxwindows.h new file mode 100644 index 000000000..fbc597313 --- /dev/null +++ b/tests/auto/blackbox/tst_blackboxwindows.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2021 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qbs. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms and +** conditions see http://www.qt.io/terms-conditions. For further information +** use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef TST_BLACKBOXWINDOWS_H +#define TST_BLACKBOXWINDOWS_H + +#include "tst_blackboxbase.h" + +class TestBlackboxWindows : public TestBlackboxBase +{ + Q_OBJECT + +public: + TestBlackboxWindows(); + +public slots: + void initTestCase() override; + +private slots: + void standaloneCodesign(); + void standaloneCodesign_data(); +}; + +#endif // TST_BLACKBOXWINDOWS_H |