1 /* commands/certificatetopivcardcommand.cpp
2
3 This file is part of Kleopatra, the KDE keymanager
4 SPDX-FileCopyrightText: 2020 g10 Code GmbH
5 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10 #include <config-kleopatra.h>
11
12 #include "certificatetopivcardcommand.h"
13
14 #include "cardcommand_p.h"
15
16 #include "commands/authenticatepivcardapplicationcommand.h"
17
18 #include "smartcard/pivcard.h"
19 #include "smartcard/readerstatus.h"
20
21 #include "utils/writecertassuantransaction.h"
22
23 #include <Libkleo/Dn>
24 #include <Libkleo/Formatting>
25 #include <Libkleo/KeyCache>
26
27 #include <KLocalizedString>
28
29 #include <qgpgme/dataprovider.h>
30
31 #include <gpgme++/context.h>
32
33 #include <gpg-error.h>
34 #if GPG_ERROR_VERSION_NUMBER >= 0x12400 // 1.36
35 # define GPG_ERROR_HAS_NO_AUTH
36 #endif
37
38 #include "kleopatra_debug.h"
39
40 using namespace Kleo;
41 using namespace Kleo::Commands;
42 using namespace Kleo::SmartCard;
43 using namespace GpgME;
44
45 class CertificateToPIVCardCommand::Private : public CardCommand::Private
46 {
47 friend class ::Kleo::Commands::CertificateToPIVCardCommand;
q_func() const48 CertificateToPIVCardCommand *q_func() const
49 {
50 return static_cast<CertificateToPIVCardCommand *>(q);
51 }
52 public:
53 explicit Private(CertificateToPIVCardCommand *qq, const std::string &slot, const std::string &serialno);
54 ~Private() override;
55
56 private:
57 void start();
58 void startCertificateToPIVCard();
59
60 void authenticate();
61 void authenticationFinished();
62 void authenticationCanceled();
63
64 private:
65 std::string cardSlot;
66 Key certificate;
67 bool hasBeenCanceled = false;
68 };
69
d_func()70 CertificateToPIVCardCommand::Private *CertificateToPIVCardCommand::d_func()
71 {
72 return static_cast<Private *>(d.get());
73 }
d_func() const74 const CertificateToPIVCardCommand::Private *CertificateToPIVCardCommand::d_func() const
75 {
76 return static_cast<const Private *>(d.get());
77 }
78
79 #define q q_func()
80 #define d d_func()
81
82
Private(CertificateToPIVCardCommand * qq,const std::string & slot,const std::string & serialno)83 CertificateToPIVCardCommand::Private::Private(CertificateToPIVCardCommand *qq, const std::string &slot, const std::string &serialno)
84 : CardCommand::Private(qq, serialno, nullptr)
85 , cardSlot(slot)
86 {
87 }
88
~Private()89 CertificateToPIVCardCommand::Private::~Private()
90 {
91 }
92
93 namespace {
getCertificateToWriteToPIVCard(const std::string & cardSlot,const std::shared_ptr<PIVCard> & card)94 static Key getCertificateToWriteToPIVCard(const std::string &cardSlot, const std::shared_ptr<PIVCard> &card)
95 {
96 if (!cardSlot.empty()) {
97 const std::string cardKeygrip = card->keyInfo(cardSlot).grip;
98 const auto certificate = KeyCache::instance()->findSubkeyByKeyGrip(cardKeygrip).parent();
99 if (certificate.isNull() || certificate.protocol() != GpgME::CMS) {
100 return Key();
101 }
102 if ((cardSlot == PIVCard::pivAuthenticationKeyRef() && certificate.canSign()) ||
103 (cardSlot == PIVCard::cardAuthenticationKeyRef() && certificate.canSign()) ||
104 (cardSlot == PIVCard::digitalSignatureKeyRef() && certificate.canSign()) ||
105 (cardSlot == PIVCard::keyManagementKeyRef() && certificate.canEncrypt())) {
106 return certificate;
107 }
108 }
109
110 return Key();
111 }
112 }
113
start()114 void CertificateToPIVCardCommand::Private::start()
115 {
116 qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::Private::start()";
117
118 const auto pivCard = SmartCard::ReaderStatus::instance()->getCard<PIVCard>(serialNumber());
119 if (!pivCard) {
120 error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber())));
121 finished();
122 return;
123 }
124
125 certificate = getCertificateToWriteToPIVCard(cardSlot, pivCard);
126 if (certificate.isNull()) {
127 error(i18n("Sorry! No suitable certificate to write to this card slot was found."));
128 finished();
129 return;
130 }
131
132 const QString certificateInfo = i18nc("X.509 certificate DN (validity, created: date)", "%1 (%2, created: %3)",
133 DN(certificate.userID(0).id()).prettyDN(),
134 Formatting::complianceStringShort(certificate),
135 Formatting::creationDateString(certificate));
136 const QString message = i18nc(
137 "@info %1 name of card slot, %2 serial number of card",
138 "<p>Please confirm that you want to write the following certificate to the %1 slot of card %2:</p>"
139 "<center>%3</center>",
140 PIVCard::keyDisplayName(cardSlot), QString::fromStdString(serialNumber()), certificateInfo);
141 auto confirmButton = KStandardGuiItem::yes();
142 confirmButton.setText(i18nc("@action:button", "Write certificate"));
143 confirmButton.setToolTip(QString());
144 const auto choice = KMessageBox::questionYesNo(
145 parentWidgetOrView(), message, i18nc("@title:window", "Write certificate to card"),
146 confirmButton, KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::WindowModal);
147 if (choice != KMessageBox::Yes) {
148 finished();
149 return;
150 }
151
152 startCertificateToPIVCard();
153 }
154
startCertificateToPIVCard()155 void CertificateToPIVCardCommand::Private::startCertificateToPIVCard()
156 {
157 qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::Private::startCertificateToPIVCard()";
158
159 auto ctx = Context::createForProtocol(GpgME::CMS);
160 QGpgME::QByteArrayDataProvider dp;
161 Data data(&dp);
162 const Error err = ctx->exportPublicKeys(certificate.primaryFingerprint(), data);
163 if (err) {
164 error(i18nc("@info", "Exporting the certificate failed: %1", QString::fromUtf8(err.asString())),
165 i18nc("@title", "Error"));
166 finished();
167 return;
168 }
169 const QByteArray certificateData = dp.data();
170
171 const auto pivCard = SmartCard::ReaderStatus::instance()->getCard<PIVCard>(serialNumber());
172 if (!pivCard) {
173 error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber())));
174 finished();
175 return;
176 }
177
178 const QByteArray command = QByteArrayLiteral("SCD WRITECERT ") + QByteArray::fromStdString(cardSlot);
179 auto transaction = std::unique_ptr<AssuanTransaction>(new WriteCertAssuanTransaction(certificateData));
180 ReaderStatus::mutableInstance()->startTransaction(pivCard, command, q_func(), "certificateToPIVCardDone", std::move(transaction));
181 }
182
authenticate()183 void CertificateToPIVCardCommand::Private::authenticate()
184 {
185 qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticate()";
186
187 auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView());
188 connect(cmd, &AuthenticatePIVCardApplicationCommand::finished,
189 q, [this]() { authenticationFinished(); });
190 connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled,
191 q, [this]() { authenticationCanceled(); });
192 cmd->start();
193 }
194
authenticationFinished()195 void CertificateToPIVCardCommand::Private::authenticationFinished()
196 {
197 qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticationFinished()";
198 if (!hasBeenCanceled) {
199 startCertificateToPIVCard();
200 }
201 }
202
authenticationCanceled()203 void CertificateToPIVCardCommand::Private::authenticationCanceled()
204 {
205 qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::authenticationCanceled()";
206 hasBeenCanceled = true;
207 canceled();
208 }
209
CertificateToPIVCardCommand(const std::string & cardSlot,const std::string & serialno)210 CertificateToPIVCardCommand::CertificateToPIVCardCommand(const std::string& cardSlot, const std::string &serialno)
211 : CardCommand(new Private(this, cardSlot, serialno))
212 {
213 }
214
~CertificateToPIVCardCommand()215 CertificateToPIVCardCommand::~CertificateToPIVCardCommand()
216 {
217 qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::~CertificateToPIVCardCommand()";
218 }
219
certificateToPIVCardDone(const Error & err)220 void CertificateToPIVCardCommand::certificateToPIVCardDone(const Error &err)
221 {
222 qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::certificateToPIVCardDone():"
223 << err.asString() << "(" << err.code() << ")";
224 if (err) {
225 #ifdef GPG_ERROR_HAS_NO_AUTH
226 // gpgme 1.13 reports "BAD PIN" instead of "NO AUTH"
227 if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) {
228 d->authenticate();
229 return;
230 }
231 #endif
232
233 d->error(i18nc("@info", "Writing the certificate to the card failed: %1", QString::fromUtf8(err.asString())),
234 i18nc("@title", "Error"));
235 } else if (!err.isCanceled()) {
236 KMessageBox::information(d->parentWidgetOrView(),
237 i18nc("@info", "Writing the certificate to the card succeeded."),
238 i18nc("@title", "Success"));
239 ReaderStatus::mutableInstance()->updateStatus();
240 }
241
242 d->finished();
243 }
244
doStart()245 void CertificateToPIVCardCommand::doStart()
246 {
247 qCDebug(KLEOPATRA_LOG) << "CertificateToPIVCardCommand::doStart()";
248
249 d->start();
250 }
251
doCancel()252 void CertificateToPIVCardCommand::doCancel()
253 {
254 }
255
256 #undef q_func
257 #undef d_func
258