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