14 void parseMultiKeyPublic();
15 void parseMultiKeyPublic_data();
16 void parseSecretKeys();
17 void parseSecretKeys_data();
18 void parseSingleKey();
19 void parseSingleKey_data();
20 void parseKeyRollover();
21 void parseKeyRollover_data();
22 void classifyRecordTypes();
23 void classifyRecordEmpty();
24 void handlePubSecEmptyFields();
25 void handlePubSecShortList();
26 void handleFprEdgeCases();
29void tst_gpgkeystate::parseMultiKeyPublic() {
30 QFETCH(QString, input);
31 QFETCH(
int, expectedCount);
33 const bool includeSecretKeys =
false;
36 QVERIFY2(result.size() == expectedCount,
37 qPrintable(QString(
"Expected %1 keys, got %2")
39 .arg(result.size())));
41 for (
const UserInfo &user : result) {
42 QVERIFY2(!user.have_secret,
43 "Public keys should not have secret capability");
46 if (expectedCount > 0) {
47 QVERIFY2(!result.at(0).key_id.isEmpty(),
"First key should have key_id");
48 QVERIFY2(!result.at(0).name.isEmpty(),
"First key should have name");
50 if (expectedCount > 1) {
51 QVERIFY2(!result.at(1).key_id.isEmpty(),
"Second key should have key_id");
52 QVERIFY2(!result.at(1).name.isEmpty(),
"Second key should have name");
56void tst_gpgkeystate::parseMultiKeyPublic_data() {
57 QTest::addColumn<QString>(
"input");
58 QTest::addColumn<int>(
"expectedCount");
60 QString input = R
"(tru::1:1775005973:0:3:1:5
61pub:u:4096:1:31850CF72D9CDDE9:1774947438:::u:::escarESCA::::::23::0:
62fpr:::::::::13A47CCE2B3DA3AC340A274A31850CF72D9CDDE9:
63uid:u::::1774947438::CBF23008234AA5F88824CE76140F482FAE34923E::Anne Jan Brouwer <henk@annejan.com>::::::::::0:
64sub:u:4096:1:6DF67C6BAD8383CB:1774947438::::::esa::::::23:
65pub:f:4096:1:693A0AF3FA364E76:1775005968:::f:::escarESCA::::::23::0:
66fpr:::::::::4EF2550F79F4E9E68B09F71D693A0AF3FA364E76:
67uid:f::::1775005968::8AA011711F27F6E08DF71653718C299A13B323A0::Harrie de Bot <harrie@annejan.com>::::::::::0:)";
69 QTest::newRow("two-public-keys") << input << 2;
72void tst_gpgkeystate::parseSecretKeys() {
73 QFETCH(QString, input);
74 QFETCH(
int, expectedCount);
75 QFETCH(
bool, expectHaveSecret);
77 const bool includeSecretKeys =
true;
80 QVERIFY2(result.size() == expectedCount,
81 qPrintable(QString(
"Expected %1 keys, got %2")
83 .arg(result.size())));
85 if (expectedCount > 0) {
86 QVERIFY(!result.isEmpty());
87 for (
int i = 0; i < result.size(); ++i) {
88 const UserInfo &user = result.at(i);
91 qPrintable(QString(
"Key at index %1 has have_secret=%2, expected %3")
94 .arg(expectHaveSecret)));
99void tst_gpgkeystate::parseSecretKeys_data() {
100 QTest::addColumn<QString>(
"input");
101 QTest::addColumn<int>(
"expectedCount");
102 QTest::addColumn<bool>(
"expectHaveSecret");
105 R
"(sec:u:4096:1:31850CF72D9CDDE9:1774947438:::u:::escarESCA:::+:::23::0:
106fpr:::::::::13A47CCE2B3DA3AC340A274A31850CF72D9CDDE9:
107uid:u::::1774947438::CBF23008234AA5F88824CE76140F482FAE34923E::Anne Jan Brouwer <henk@annejan.com>::::::::::0:
108ssb:u:4096:1:6DF67C6BAD8383CB:1774947438::::::esa:::+:::23:)";
110 QTest::newRow("single-secret-key") << input << 1 <<
true;
113void tst_gpgkeystate::parseSingleKey() {
114 QFETCH(QString, input);
115 QFETCH(
int, expectedCount);
116 QFETCH(QString, expectedKeyId);
118 const bool includeSecretKeys =
false;
120 QVERIFY2(result.size() == expectedCount,
121 qPrintable(QString(
"Expected %1 keys, got %2")
123 .arg(result.size())));
125 if (!result.isEmpty()) {
126 QVERIFY2(!result.first().key_id.isEmpty(),
127 "Parsed key should have a key_id");
128 if (!expectedKeyId.isEmpty()) {
129 QVERIFY2(result.first().key_id == expectedKeyId,
130 qPrintable(QString(
"Expected key_id %1, got %2")
132 .arg(result.first().key_id)));
137void tst_gpgkeystate::parseSingleKey_data() {
138 QTest::addColumn<QString>(
"input");
139 QTest::addColumn<int>(
"expectedCount");
140 QTest::addColumn<QString>(
"expectedKeyId");
142 QTest::newRow(
"pub-only")
143 <<
"pub:u:4096:1:ABC123:1774947438:::u::::::23::0:" << 1 <<
"ABC123";
144 QTest::newRow(
"pub-with-fpr") <<
"pub:u:4096:1:ABC123:1774947438:::u::::::23:"
145 ":0:\nfpr:::::::::FINGERPRINT123456789:"
149void tst_gpgkeystate::parseKeyRollover() {
150 QFETCH(QString, input);
151 QFETCH(
int, expectedCount);
153 const bool includeSecretKeys =
false;
155 QVERIFY2(result.size() == expectedCount,
156 qPrintable(QString(
"Expected %1 keys, got %2")
158 .arg(result.size())));
160 auto containsKeyId = [&](
const QString &keyId) {
162 result.cbegin(), result.cend(),
163 [&](
const UserInfo &user) { return user.key_id == keyId; });
166 QVERIFY2(containsKeyId(
"AAA111"),
"Expected AAA111 key to be parsed");
167 QVERIFY2(containsKeyId(
"BBB222"),
"Expected BBB222 key to be parsed");
168 QVERIFY2(containsKeyId(
"CCC333"),
"Expected CCC333 key to be parsed");
171void tst_gpgkeystate::parseKeyRollover_data() {
172 QTest::addColumn<QString>(
"input");
173 QTest::addColumn<int>(
"expectedCount");
175 QString input = R
"(pub:u:4096:1:AAA111:1774947438:::u::::
176fpr:::::::::AAA111FINGERPRINT:
177uid:u::::1774947438::NAME1::user1@test.com:::::::0:
178pub:u:4096:1:BBB222:1774947438:::u::::
179fpr:::::::::BBB222FINGERPRINT:
180uid:u::::1774947438::NAME2::user2@test.com:::::::0:
181pub:u:4096:1:CCC333:1774947438:::u::::
182fpr:::::::::CCC333FINGERPRINT:
183uid:u::::1774947438::NAME3::user3@test.com:::::::0:)";
185 QTest::newRow("three-keys-rollover") << input << 3;
188void tst_gpgkeystate::classifyRecordTypes() {
190 "Should classify pub record");
192 "Should classify sec record");
194 "Should classify uid record");
196 "Should classify fpr record");
198 "Should classify sub record");
200 "Should classify ssb record");
202 "Should classify grp record");
204 "Should classify unknown record types as Unknown");
207void tst_gpgkeystate::classifyRecordEmpty() {
209 "Should classify empty as Unknown");
211 "Should be case-sensitive");
213 "Should not match partial");
216void tst_gpgkeystate::handlePubSecEmptyFields() {
232 QVERIFY2(user.
key_id ==
"keyId00001",
"Should parse key_id");
233 QVERIFY2(user.
name ==
"Test User",
"Should parse name");
234 QVERIFY2(user.
validity ==
'-',
"Empty validity should be dash");
235 QVERIFY2(!user.
created.isValid(),
"Empty created should be invalid");
236 QVERIFY2(!user.
expiry.isValid(),
"Empty expiry should be invalid");
237 QVERIFY2(!user.
have_secret,
"Should not have secret");
240void tst_gpgkeystate::handlePubSecShortList() {
241 QStringList shortProps;
242 shortProps.append(
"pub");
243 shortProps.append(
"");
244 shortProps.append(
"4096");
245 shortProps.append(
"1");
246 shortProps.append(
"");
248 auto verifyShortListIgnored = [](
const UserInfo &u,
const char *ctx) {
249 QVERIFY2(u.
key_id.isEmpty(),
"Short list should be ignored");
250 QVERIFY2(u.
name.isEmpty(),
"Short list ignored: name empty");
251 QVERIFY2(u.
validity ==
'-',
"Short list ignored: default validity");
252 QVERIFY2(!u.
created.isValid(),
"Short list ignored: created invalid");
253 QVERIFY2(!u.
expiry.isValid(),
"Short list ignored: expiry invalid");
259 verifyShortListIgnored(publicUser,
260 "Short list ignored: no secret for public");
264 verifyShortListIgnored(secretUser,
265 "Short list ignored: no secret for secret input");
268void tst_gpgkeystate::handleFprEdgeCases() {
272 QStringList emptyProps;
273 for (
int i = 0; i < 10; ++i)
274 emptyProps.append(
"");
276 QVERIFY2(user.
key_id ==
"id123",
"Empty fpr should not change key_id");
278 QStringList nonMatchingProps;
279 for (
int i = 0; i < 10; ++i)
280 nonMatchingProps.append(
"");
281 nonMatchingProps[9] =
"otherFingerprint";
283 QVERIFY2(user.
key_id ==
"id123",
"Non-matching fpr should not change key_id");
286 QStringList matchingProps;
287 for (
int i = 0; i < 10; ++i)
288 matchingProps.append(
"");
289 matchingProps[9] =
"full fingerprint id456";
291 QVERIFY2(user.
key_id ==
"full fingerprint id456",
292 "fpr ending with key_id should update to full fingerprint");
296#include "tst_gpgkeystate.moc"
void handlePubSecRecord(const QStringList &props, bool secret, UserInfo ¤t_user)
Handle a pub or sec record in GPG colon output.
auto parseGpgColonOutput(const QString &output, bool secret) -> QList< UserInfo >
Parse GPG –with-colons output into a list of UserInfo.
auto classifyRecord(const QString &record_type) -> GpgRecordType
Classify a GPG colon output record type.
void handleFprRecord(const QStringList &props, UserInfo ¤t_user)
Handle an fpr (fingerprint) record in GPG colon output.
Stores key info lines including validity, creation date and more.
bool have_secret
UserInfo::have_secret whether secret key is available (can decrypt with this key).
QString key_id
UserInfo::key_id hexadecimal representation of the GnuPG key identifier.
QDateTime created
UserInfo::created date/time when key was created.
QString name
UserInfo::name GPG user ID / full name.
char validity
UserInfo::validity GnuPG representation of validity http://git.gnupg.org/cgi-bin/gitweb....
QDateTime expiry
UserInfo::expiry date/time when key expires.