Messagelib

nearexpirychecker.cpp
1 /*
2  SPDX-FileCopyrightText: 2021 Sandro KnauƟ <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "composer/nearexpirychecker.h"
8 #include "composer/nearexpirychecker_p.h"
9 
10 #include "messagecomposer_debug.h"
11 
12 #include <Libkleo/Dn>
13 
14 #include <QGpgME/KeyListJob>
15 #include <QGpgME/Protocol>
16 
17 #include <gpgme++/keylistresult.h>
18 
19 #include <KLocalizedString>
20 
21 #include <QString>
22 
23 using namespace MessageComposer;
24 
25 NearExpiryChecker::NearExpiryChecker(int encrOwnKeyNearExpiryThresholdDays,
26  int encrKeyNearExpiryThresholdDays,
27  int encrRootCertNearExpiryThresholdDays,
28  int encrChainCertNearExpiryThresholdDays)
29  : d(new NearExpiryCheckerPrivate)
30 {
31  d->encryptOwnKeyNearExpiryWarningThreshold = encrOwnKeyNearExpiryThresholdDays;
32  d->encryptKeyNearExpiryWarningThreshold = encrKeyNearExpiryThresholdDays;
33  d->encryptRootCertNearExpiryWarningThreshold = encrRootCertNearExpiryThresholdDays;
34  d->encryptChainCertNearExpiryWarningThreshold = encrChainCertNearExpiryThresholdDays;
35 }
36 
37 NearExpiryChecker::~NearExpiryChecker() = default;
38 
39 int NearExpiryChecker::encryptOwnKeyNearExpiryWarningThresholdInDays() const
40 {
41  return d->encryptOwnKeyNearExpiryWarningThreshold;
42 }
43 
44 int NearExpiryChecker::encryptKeyNearExpiryWarningThresholdInDays() const
45 {
46  return d->encryptKeyNearExpiryWarningThreshold;
47 }
48 
49 int NearExpiryChecker::encryptRootCertNearExpiryWarningThresholdInDays() const
50 {
51  return d->encryptRootCertNearExpiryWarningThreshold;
52 }
53 
54 int NearExpiryChecker::encryptChainCertNearExpiryWarningThresholdInDays() const
55 {
56  return d->encryptChainCertNearExpiryWarningThreshold;
57 }
58 
59 QString formatOpenPGPMessage(const GpgME::Key &key, int secsTillExpiry, bool isOwnKey, bool isSigningKey)
60 {
61  KLocalizedString msg;
62  static const double secsPerDay = 24 * 60 * 60;
63  const int daysTillExpiry = 1 + int(abs(secsTillExpiry) / secsPerDay);
64  if (secsTillExpiry <= 0) {
65  qCDebug(MESSAGECOMPOSER_LOG) << "Key 0x" << key.keyID() << " expired " << daysTillExpiry << " days ago";
66  if (isSigningKey) {
67  msg = ki18np(
68  "<p>Your OpenPGP signing key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
69  "<p>expired less than a day ago.</p>",
70  "<p>Your OpenPGP signing key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
71  "<p>expired %1 days ago.</p>");
72  } else if (isOwnKey) {
73  msg = ki18np(
74  "<p>Your OpenPGP encryption key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
75  "<p>expired less than a day ago.</p>",
76  "<p>Your OpenPGP encryption key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
77  "<p>expired %1 days ago.</p>");
78  } else {
79  msg = ki18np(
80  "<p>The OpenPGP key for</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
81  "<p>expired less than a day ago.</p>",
82  "<p>The OpenPGP key for</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
83  "<p>expired %1 days ago.</p>");
84  }
85  } else {
86  qCDebug(MESSAGECOMPOSER_LOG) << "Key 0x" << key.keyID() << " expires in less than " << daysTillExpiry << " days";
87  if (isSigningKey) {
88  msg = ki18np(
89  "<p>Your OpenPGP signing key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
90  "<p>expires in less than a day.</p>",
91  "<p>Your OpenPGP signing key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
92  "<p>expires in less than %1 days.</p>");
93  } else if (isOwnKey) {
94  msg = ki18np(
95  "<p>Your OpenPGP encryption key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
96  "<p>expires in less than a day.</p>",
97  "<p>Your OpenPGP encryption key</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
98  "<p>expires in less than %1 days.</p>");
99  } else {
100  msg = ki18np(
101  "<p>The OpenPGP key for</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
102  "<p>expires in less than a day.</p>",
103  "<p>The OpenPGP key for</p><p align=center><b>%2</b> (KeyID 0x%3)</p>"
104  "<p>expires in less than %1 days.</p>");
105  }
106  }
107  return msg.subs(daysTillExpiry).subs(QString::fromUtf8(key.userID(0).id())).subs(QString::fromLatin1(key.keyID())).toString();
108 }
109 
110 QString formatSMIMEMessage(const GpgME::Key &key, const GpgME::Key &orig_key, int secsTillExpiry, bool isOwnKey, bool isSigningKey, bool ca)
111 {
112  KLocalizedString msg;
113  static const double secsPerDay = 24 * 60 * 60;
114  const int daysTillExpiry = 1 + int(abs(secsTillExpiry) / secsPerDay);
115  if (secsTillExpiry <= 0) {
116  qCDebug(MESSAGECOMPOSER_LOG) << "Key 0x" << key.keyID() << " expired " << daysTillExpiry << " days ago";
117  if (ca) {
118  if (key.isRoot()) {
119  if (isSigningKey) {
120  msg = ki18np(
121  "<p>The root certificate</p><p align=center><b>%4</b></p>"
122  "<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
123  "<p>expired less than a day ago.</p>",
124  "<p>The root certificate</p><p align=center><b>%4</b></p>"
125  "<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
126  "<p>expired %1 days ago.</p>");
127  } else if (isOwnKey) {
128  msg = ki18np(
129  "<p>The root certificate</p><p align=center><b>%4</b></p>"
130  "<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
131  "<p>expired less than a day ago.</p>",
132  "<p>The root certificate</p><p align=center><b>%4</b></p>"
133  "<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
134  "<p>expired %1 days ago.</p>");
135  } else {
136  msg = ki18np(
137  "<p>The root certificate</p><p align=center><b>%4</b></p>"
138  "<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
139  "<p>expired less than a day ago.</p>",
140  "<p>The root certificate</p><p align=center><b>%4</b></p>"
141  "<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
142  "<p>expired %1 days ago.</p>");
143  }
144  } else {
145  if (isSigningKey) {
146  msg = ki18np(
147  "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
148  "<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
149  "<p>expired less than a day ago.</p>",
150  "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
151  "<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
152  "<p>expired %1 days ago.</p>");
153  } else if (isOwnKey) {
154  msg = ki18np(
155  "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
156  "<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
157  "<p>expired less than a day ago.</p>",
158  "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
159  "<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
160  "<p>expired %1 days ago.</p>");
161  } else {
162  msg = ki18np(
163  "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
164  "<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
165  "<p>expired less than a day ago.</p>",
166  "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
167  "<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
168  "<p>expired %1 days ago.</p>");
169  }
170  }
171  return msg.subs(daysTillExpiry)
172  .subs(Kleo::DN(orig_key.userID(0).id()).prettyDN())
173  .subs(QString::fromLatin1(orig_key.issuerSerial()))
174  .subs(Kleo::DN(key.userID(0).id()).prettyDN())
175  .toString();
176  } else {
177  if (isSigningKey) {
178  msg = ki18np(
179  "<p>Your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
180  "<p>expired less than a day ago.</p>",
181  "<p>Your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
182  "<p>expired %1 days ago.</p>");
183  } else if (isOwnKey) {
184  msg = ki18np(
185  "<p>Your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
186  "<p>expired less than a day ago.</p>",
187  "<p>Your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3)</p>"
188  "<p>expired %1 days ago.</p>");
189  } else {
190  msg = ki18np(
191  "<p>The S/MIME certificate for</p><p align=center><b>%2</b> (serial number %3)</p>"
192  "<p>expired less than a day ago.</p>",
193  "<p>The S/MIME certificate for</p><p align=center><b>%2</b> (serial number %3)</p>"
194  "<p>expired %1 days ago.</p>");
195  }
196  return msg.subs(daysTillExpiry).subs(Kleo::DN(key.userID(0).id()).prettyDN()).subs(QString::fromLatin1(key.issuerSerial())).toString();
197  }
198  } else {
199  qCDebug(MESSAGECOMPOSER_LOG) << "Key 0x" << key.keyID() << " expires in less than " << daysTillExpiry << " days";
200  if (ca) {
201  if (key.isRoot()) {
202  if (isSigningKey) {
203  msg = ki18np(
204  "<p>The root certificate</p><p align=center><b>%4</b></p>"
205  "<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
206  "<p>expires in less than a day.</p>",
207  "<p>The root certificate</p><p align=center><b>%4</b></p>"
208  "<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
209  "<p>expires in less than %1 days.</p>");
210  } else if (isOwnKey) {
211  msg = ki18np(
212  "<p>The root certificate</p><p align=center><b>%4</b></p>"
213  "<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
214  "<p>expires in less than a day.</p>",
215  "<p>The root certificate</p><p align=center><b>%4</b></p>"
216  "<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
217  "<p>expires in less than %1 days.</p>");
218  } else {
219  msg = ki18np(
220  "<p>The root certificate</p><p align=center><b>%4</b></p>"
221  "<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
222  "<p>expires in less than a day.</p>",
223  "<p>The root certificate</p><p align=center><b>%4</b></p>"
224  "<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
225  "<p>expires in less than %1 days.</p>");
226  }
227  } else {
228  if (isSigningKey) {
229  msg = ki18np(
230  "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
231  "<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
232  "<p>expires in less than a day.</p>",
233  "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
234  "<p>for your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
235  "<p>expires in less than %1 days.</p>");
236  } else if (isOwnKey) {
237  msg = ki18np(
238  "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
239  "<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
240  "<p>expires in less than a day.</p>",
241  "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
242  "<p>for your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
243  "<p>expires in less than %1 days.</p>");
244  } else {
245  msg = ki18np(
246  "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
247  "<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
248  "<p>expires in less than a day.</p>",
249  "<p>The intermediate CA certificate</p><p align=center><b>%4</b></p>"
250  "<p>for S/MIME certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
251  "<p>expires in less than %1 days.</p>");
252  }
253  }
254  return msg.subs(daysTillExpiry)
255  .subs(Kleo::DN(orig_key.userID(0).id()).prettyDN())
256  .subs(QString::fromLatin1(orig_key.issuerSerial()))
257  .subs(Kleo::DN(key.userID(0).id()).prettyDN())
258  .toString();
259  } else {
260  if (isSigningKey) {
261  msg = ki18np(
262  "<p>Your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
263  "<p>expires in less than a day.</p>",
264  "<p>Your S/MIME signing certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
265  "<p>expires in less than %1 days.</p>");
266  } else if (isOwnKey) {
267  msg = ki18np(
268  "<p>Your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
269  "<p>expires in less than a day.</p>",
270  "<p>Your S/MIME encryption certificate</p><p align=center><b>%2</b> (serial number %3);</p>"
271  "<p>expires in less than %1 days.</p>");
272  } else {
273  msg = ki18np(
274  "<p>The S/MIME certificate for</p><p align=center><b>%2</b> (serial number %3);</p>"
275  "<p>expires in less than a day.</p>",
276  "<p>The S/MIME certificate for</p><p align=center><b>%2</b> (serial number %3);</p>"
277  "<p>expires in less than %1 days.</p>");
278  }
279  return msg.subs(daysTillExpiry).subs(Kleo::DN(key.userID(0).id()).prettyDN()).subs(QString::fromLatin1(key.issuerSerial())).toString();
280  }
281  }
282 }
283 
284 double MessageComposer::NearExpiryChecker::calculateSecsTillExpiriy(const GpgME::Subkey &key) const
285 {
286  if (d->testMode) {
287  return d->difftime;
288  }
289 
290  return ::difftime(key.expirationTime(), time(nullptr));
291 }
292 
293 void NearExpiryChecker::checkKeyNearExpiry(const GpgME::Key &key, bool isOwnKey, bool isSigningKey, bool ca, int recur_limit, const GpgME::Key &orig_key) const
294 {
295  if (recur_limit <= 0) {
296  qCDebug(MESSAGECOMPOSER_LOG) << "Key chain too long (>100 certs)";
297  return;
298  }
299  const GpgME::Subkey subkey = key.subkey(0);
300 
301  const bool newMessage = !d->alreadyWarnedFingerprints.count(subkey.fingerprint());
302 
303  if (subkey.neverExpires()) {
304  return;
305  }
306  static const double secsPerDay = 24 * 60 * 60;
307  const double secsTillExpiry = calculateSecsTillExpiriy(subkey);
308  if (secsTillExpiry <= 0) {
309  const QString msg = key.protocol() == GpgME::OpenPGP ? formatOpenPGPMessage(key, secsTillExpiry, isOwnKey, isSigningKey)
310  : formatSMIMEMessage(key, orig_key, secsTillExpiry, isOwnKey, isSigningKey, ca);
311  d->alreadyWarnedFingerprints.insert(subkey.fingerprint());
312  Q_EMIT expiryMessage(key, msg, isOwnKey ? OwnKeyExpired : OtherKeyExpired, newMessage);
313  } else {
314  const int daysTillExpiry = 1 + int(secsTillExpiry / secsPerDay);
315  const int threshold = ca ? (key.isRoot() ? encryptRootCertNearExpiryWarningThresholdInDays() : encryptChainCertNearExpiryWarningThresholdInDays())
316  : (isOwnKey ? encryptOwnKeyNearExpiryWarningThresholdInDays() : encryptKeyNearExpiryWarningThresholdInDays());
317  if (threshold > -1 && daysTillExpiry <= threshold) {
318  const QString msg = key.protocol() == GpgME::OpenPGP ? formatOpenPGPMessage(key, secsTillExpiry, isOwnKey, isSigningKey)
319  : formatSMIMEMessage(key, orig_key, secsTillExpiry, isOwnKey, isSigningKey, ca);
320  d->alreadyWarnedFingerprints.insert(subkey.fingerprint());
321  Q_EMIT expiryMessage(key, msg, isOwnKey ? OwnKeyNearExpiry : OtherKeyNearExpiry, newMessage);
322  }
323  }
324  if (key.isRoot()) {
325  return;
326  } else if (key.protocol() != GpgME::CMS) { // Key chaining is only possible in SMIME
327  return;
328  } else if (const char *chain_id = key.chainID()) {
329  QGpgME::Protocol *p = QGpgME::smime();
330  Q_ASSERT(p);
331  std::unique_ptr<QGpgME::KeyListJob> job(p->keyListJob(false, false, true));
332  if (job.get()) {
333  std::vector<GpgME::Key> keys;
334  job->exec(QStringList(QLatin1String(chain_id)), false, keys);
335  if (!keys.empty()) {
336  return checkKeyNearExpiry(keys.front(), isOwnKey, isSigningKey, true, recur_limit - 1, ca ? orig_key : key);
337  }
338  }
339  }
340 }
341 
342 void NearExpiryChecker::checkOwnSigningKey(const GpgME::Key &key) const
343 {
344  checkKeyNearExpiry(key, /*isOwnKey*/ true, /*isSigningKey*/ true);
345 }
346 
347 void NearExpiryChecker::checkOwnKey(const GpgME::Key &key) const
348 {
349  checkKeyNearExpiry(key, /*isOwnKey*/ true, /*isSigningKey*/ false);
350 }
351 
352 void NearExpiryChecker::checkKey(const GpgME::Key &key) const
353 {
354  checkKeyNearExpiry(key, false, false);
355 }
Simple interface that both EncryptJob and SignEncryptJob implement so the composer can extract some e...
QString fromUtf8(const char *str, int size)
QString toString() const
KLocalizedString subs(const KLocalizedString &a, int fieldWidth=0, QChar fillChar=QLatin1Char(' ')) const
QString prettyDN() const
QString & insert(int position, QChar ch)
QString fromLatin1(const char *str, int size)
KLocalizedString KI18N_EXPORT ki18np(const char *singular, const char *plural)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sat Apr 1 2023 04:01:57 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.