diff --git a/NEWS b/NEWS index cd3cd6e6..3ae4f9f4 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,8 @@ Noteworthy changes in version 1.16.1 (unreleased) * cpp,qt: Add support for export of secret keys and secret subkeys. [#5757] + * cpp: Support for adding existing subkeys to other keys. [#5770] + * qt: Extend ChangeExpiryJob to change expiration of primary key and of subkeys at the same time. [#4717] @@ -25,6 +27,7 @@ Noteworthy changes in version 1.16.1 (unreleased) cpp: Context::startKeyExport NEW. cpp: Context::exportSecretKeys NEW. cpp: Context::startSecretKeyExport NEW. + cpp: GpgAddExistingSubkeyEditInteractor NEW. GPGME_EXPORT_MODE_SECRET_SUBKEY NEW. gpgme_set_ctx_flag EXTENDED: New flag 'key-origin'. gpgme_set_ctx_flag EXTENDED: New flag 'import-filter'. diff --git a/lang/cpp/src/Makefile.am b/lang/cpp/src/Makefile.am index 32e3045c..7af66325 100644 --- a/lang/cpp/src/Makefile.am +++ b/lang/cpp/src/Makefile.am @@ -33,6 +33,7 @@ main_sources = \ engineinfo.cpp gpgsetexpirytimeeditinteractor.cpp \ gpgsetownertrusteditinteractor.cpp gpgsignkeyeditinteractor.cpp \ gpgadduserideditinteractor.cpp gpggencardkeyinteractor.cpp \ + gpgaddexistingsubkeyeditinteractor.cpp \ defaultassuantransaction.cpp \ scdgetinfoassuantransaction.cpp gpgagentgetinfoassuantransaction.cpp \ statusconsumerassuantransaction.cpp \ @@ -46,6 +47,7 @@ gpgmepp_headers = \ gpgmefw.h gpgsetexpirytimeeditinteractor.h \ gpgsetownertrusteditinteractor.h gpgsignkeyeditinteractor.h \ gpggencardkeyinteractor.h \ + gpgaddexistingsubkeyeditinteractor.h \ importresult.h keygenerationresult.h key.h keylistresult.h \ notation.h result.h scdgetinfoassuantransaction.h signingresult.h \ statusconsumerassuantransaction.h \ diff --git a/lang/cpp/src/gpgaddexistingsubkeyeditinteractor.cpp b/lang/cpp/src/gpgaddexistingsubkeyeditinteractor.cpp new file mode 100644 index 00000000..547e613d --- /dev/null +++ b/lang/cpp/src/gpgaddexistingsubkeyeditinteractor.cpp @@ -0,0 +1,209 @@ +/* + gpgaddexistingsubkeyeditinteractor.cpp - Edit Interactor to add an existing subkey to an OpenPGP key + Copyright (c) 2022 g10 Code GmbH + Software engineering by Ingo Klöcker + + This file is part of GPGME++. + + GPGME++ is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + GPGME++ is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with GPGME++; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "gpgaddexistingsubkeyeditinteractor.h" + +#include "error.h" + +#include + +// avoid conflict (msvc) +#ifdef ERROR +# undef ERROR +#endif + +using namespace GpgME; + +class GpgAddExistingSubkeyEditInteractor::Private +{ + enum { + START = EditInteractor::StartState, + COMMAND, + ADD_EXISTING_KEY, + KEYGRIP, + FLAGS, + VALID, + KEY_CREATED, + QUIT, + SAVE, + + ERROR = EditInteractor::ErrorState + }; + + GpgAddExistingSubkeyEditInteractor *const q = nullptr; + +public: + Private(GpgAddExistingSubkeyEditInteractor *q, const std::string &keygrip) + : q{q} + , keygrip{keygrip} + { + } + + const char *action(Error &err) const; + unsigned int nextState(unsigned int statusCode, const char *args, Error &err) const; + + std::string keygrip; + std::string expiry; +}; + +const char *GpgAddExistingSubkeyEditInteractor::Private::action(Error &err) const +{ + switch (q->state()) { + case COMMAND: + return "addkey"; + case ADD_EXISTING_KEY: + return "keygrip"; + case KEYGRIP: + return keygrip.c_str(); + case FLAGS: + return "Q"; // do not toggle any usage flags + case VALID: + return expiry.empty() ? "0" : expiry.c_str(); + case QUIT: + return "quit"; + case SAVE: + return "Y"; + case START: + case KEY_CREATED: + case ERROR: + return nullptr; + default: + err = Error::fromCode(GPG_ERR_GENERAL); + return nullptr; + } +} + +unsigned int GpgAddExistingSubkeyEditInteractor::Private::nextState(unsigned int status, const char *args, Error &err) const +{ + using std::strcmp; + + static const Error GENERAL_ERROR = Error::fromCode(GPG_ERR_GENERAL); + static const Error NO_KEY_ERROR = Error::fromCode(GPG_ERR_NO_KEY); + static const Error INV_TIME_ERROR = Error::fromCode(GPG_ERR_INV_TIME); + + if (q->needsNoResponse(status)) { + return q->state(); + } + + switch (q->state()) { + case START: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keyedit.prompt") == 0) { + return COMMAND; + } + err = GENERAL_ERROR; + return ERROR; + case COMMAND: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.algo") == 0) { + return ADD_EXISTING_KEY; + } + err = GENERAL_ERROR; + return ERROR; + case ADD_EXISTING_KEY: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.keygrip") == 0) { + return KEYGRIP; + } + err = GENERAL_ERROR; + return ERROR; + case KEYGRIP: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.flags") == 0) { + return FLAGS; + } else if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.keygrip")) { + err = NO_KEY_ERROR; + return ERROR; + } + err = GENERAL_ERROR; + return ERROR; + case FLAGS: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.valid") == 0) { + return VALID; + } + err = GENERAL_ERROR; + return ERROR; + case VALID: + if (status == GPGME_STATUS_KEY_CREATED) { + return KEY_CREATED; + } + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keyedit.prompt") == 0) { + return QUIT; + } else if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keygen.valid")) { + err = INV_TIME_ERROR; + return ERROR; + } + err = GENERAL_ERROR; + return ERROR; + case KEY_CREATED: + return QUIT; + case QUIT: + if (status == GPGME_STATUS_GET_BOOL && + strcmp(args, "keyedit.save.okay") == 0) { + return SAVE; + } + err = GENERAL_ERROR; + return ERROR; + case ERROR: + if (status == GPGME_STATUS_GET_LINE && + strcmp(args, "keyedit.prompt") == 0) { + return QUIT; + } + err = q->lastError(); + return ERROR; + default: + err = GENERAL_ERROR; + return ERROR; + } +} + +GpgAddExistingSubkeyEditInteractor::GpgAddExistingSubkeyEditInteractor(const std::string &keygrip) + : EditInteractor{} + , d{new Private{this, keygrip}} +{ +} + +GpgAddExistingSubkeyEditInteractor::~GpgAddExistingSubkeyEditInteractor() = default; + +void GpgAddExistingSubkeyEditInteractor::setExpiry(const std::string &timeString) +{ + d->expiry = timeString; +} + +const char *GpgAddExistingSubkeyEditInteractor::action(Error &err) const +{ + return d->action(err); +} + +unsigned int GpgAddExistingSubkeyEditInteractor::nextState(unsigned int status, const char *args, Error &err) const +{ + return d->nextState(status, args, err); +} diff --git a/lang/cpp/src/gpgaddexistingsubkeyeditinteractor.h b/lang/cpp/src/gpgaddexistingsubkeyeditinteractor.h new file mode 100644 index 00000000..c30f18e4 --- /dev/null +++ b/lang/cpp/src/gpgaddexistingsubkeyeditinteractor.h @@ -0,0 +1,59 @@ +/* + gpgaddexistingsubkeyeditinteractor.h - Edit Interactor to add an existing subkey to an OpenPGP key + Copyright (c) 2022 g10 Code GmbH + Software engineering by Ingo Klöcker + + This file is part of GPGME++. + + GPGME++ is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + GPGME++ is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with GPGME++; see the file COPYING.LIB. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef __GPGMEPP_GPGADDEXISTINGSUBKEYEDITINTERACTOR_H__ +#define __GPGMEPP_GPGADDEXISTINGSUBKEYEDITINTERACTOR_H__ + +#include "editinteractor.h" + +#include + +namespace GpgME +{ + +class GPGMEPP_EXPORT GpgAddExistingSubkeyEditInteractor : public EditInteractor +{ +public: + /** Edit interactor to add the existing subkey with keygrip \a keygrip + * to the key a key edit operation is working on. + **/ + explicit GpgAddExistingSubkeyEditInteractor(const std::string &keygrip); + ~GpgAddExistingSubkeyEditInteractor() override; + + /** Sets the validity period of the added subkey. Use "0" for no expiration + * or a simplified ISO date string ("yyyymmddThhmmss") for setting an + * expiration date. */ + void setExpiry(const std::string &timeString); + +private: + const char *action(Error &err) const override; + unsigned int nextState(unsigned int statusCode, const char *args, Error &err) const override; + +private: + class Private; + const std::unique_ptr d; +}; + +} // namespace GpgME + +#endif // __GPGMEPP_GPGADDEXISTINGSUBKEYEDITINTERACTOR_H__