diff --git a/lang/qt/tests/t-various.cpp b/lang/qt/tests/t-various.cpp index dc5a2d1c..1e6bba41 100644 --- a/lang/qt/tests/t-various.cpp +++ b/lang/qt/tests/t-various.cpp @@ -1,254 +1,263 @@ /* t-various.cpp This file is part of qgpgme, the Qt API binding for gpgme Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH QGpgME is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. QGpgME 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include "keylistjob.h" #include "protocol.h" #include "keylistresult.h" #include "context.h" #include "engineinfo.h" #include "dn.h" #include "data.h" #include "dataprovider.h" #include "t-support.h" using namespace QGpgME; using namespace GpgME; static const char aKey[] = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" "\n" "mDMEWG+w/hYJKwYBBAHaRw8BAQdAiq1oStvDYg8ZfFs5DgisYJo8dJxD+C/AA21O\n" "K/aif0O0GXRvZnVfY29uZmxpY3RAZXhhbXBsZS5jb22IlgQTFggAPhYhBHoJBLaV\n" "DamYAgoa1L5BwMOl/x88BQJYb7D+AhsDBQkDwmcABQsJCAcCBhUICQoLAgQWAgMB\n" "Ah4BAheAAAoJEL5BwMOl/x88GvwA/0SxkbLyAcshGm2PRrPsFQsSVAfwaSYFVmS2\n" "cMVIw1PfAQDclRH1Z4MpufK07ju4qI33o4s0UFpVRBuSxt7A4P2ZD7g4BFhvsP4S\n" "CisGAQQBl1UBBQEBB0AmVrgaDNJ7K2BSalsRo2EkRJjHGqnp5bBB0tapnF81CQMB\n" "CAeIeAQYFggAIBYhBHoJBLaVDamYAgoa1L5BwMOl/x88BQJYb7D+AhsMAAoJEL5B\n" "wMOl/x88OR0BAMq4/vmJUORRTmzjHcv/DDrQB030DSq666rlckGIKTShAPoDXM9N\n" "0gZK+YzvrinSKZXHmn0aSwmC1/hyPybJPEljBw==\n" "=p2Oj\n" "-----END PGP PUBLIC KEY BLOCK-----\n"; class TestVarious: public QGpgMETest { Q_OBJECT Q_SIGNALS: void asyncDone(); private Q_SLOTS: void testDN() { DN dn(QStringLiteral("CN=Before\\0DAfter,OU=Test,DC=North America,DC=Fabrikam,DC=COM")); QVERIFY(dn.dn() == QStringLiteral("CN=Before\rAfter,OU=Test,DC=North America,DC=Fabrikam,DC=COM")); QStringList attrOrder; attrOrder << QStringLiteral("DC") << QStringLiteral("OU") << QStringLiteral("CN"); dn.setAttributeOrder(attrOrder); QVERIFY(dn.prettyDN() == QStringLiteral("DC=North America,DC=Fabrikam,DC=COM,OU=Test,CN=Before\rAfter")); } void testKeyFromFile() { if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.14") { return; } QGpgME::QByteArrayDataProvider dp(aKey); Data data(&dp); const auto keys = data.toKeys(); QVERIFY(keys.size() == 1); const auto key = keys[0]; QVERIFY(!key.isNull()); QVERIFY(key.primaryFingerprint() == QStringLiteral("7A0904B6950DA998020A1AD4BE41C0C3A5FF1F3C")); } void testDataRewind() { if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.14") { return; } QGpgME::QByteArrayDataProvider dp(aKey); Data data(&dp); char buf[20]; data.read(buf, 20); auto keys = data.toKeys(); QVERIFY(keys.size() == 0); data.rewind(); keys = data.toKeys(); QVERIFY(keys.size() == 1); } void testQuickUid() { if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.13") { return; } KeyListJob *job = openpgp()->keyListJob(false, true, true); std::vector keys; GpgME::KeyListResult result = job->exec(QStringList() << QStringLiteral("alfa@example.net"), false, keys); delete job; QVERIFY (!result.error()); QVERIFY (keys.size() == 1); Key key = keys.front(); QVERIFY (key.numUserIDs() == 3); const char uid[] = "Foo Bar (with comment) "; auto ctx = Context::createForProtocol(key.protocol()); QVERIFY (ctx); TestPassphraseProvider provider; ctx->setPassphraseProvider(&provider); ctx->setPinentryMode(Context::PinentryLoopback); QVERIFY(!ctx->addUid(key, uid)); delete ctx; key.update(); QVERIFY (key.numUserIDs() == 4); bool id_found = false;; for (const auto &u: key.userIDs()) { if (!strcmp (u.id(), uid)) { QVERIFY (!u.isRevoked()); id_found = true; break; } } QVERIFY (id_found); ctx = Context::createForProtocol(key.protocol()); QVERIFY (!ctx->revUid(key, uid)); delete ctx; key.update(); bool id_revoked = false;; for (const auto &u: key.userIDs()) { if (!strcmp (u.id(), uid)) { id_revoked = true; break; } } QVERIFY(id_revoked); } void testSetExpire() { if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.22") { return; } KeyListJob *job = openpgp()->keyListJob(false, true, true); std::vector keys; GpgME::KeyListResult result = job->exec(QStringList() << QStringLiteral("alfa@example.net"), false, keys); delete job; QVERIFY (!result.error()); QVERIFY (keys.size() == 1); Key key = keys.front(); QVERIFY (key.subkey(0).expirationTime() == time_t(0)); QVERIFY (key.subkey(1).expirationTime() == time_t(0)); auto ctx = Context::createForProtocol(key.protocol()); QVERIFY (ctx); TestPassphraseProvider provider; ctx->setPassphraseProvider(&provider); ctx->setPinentryMode(Context::PinentryLoopback); // change expiration of the main key QVERIFY(!ctx->setExpire(key, 1000)); delete ctx; key.update(); QVERIFY (key.subkey(0).expirationTime() != time_t(0)); QVERIFY (key.subkey(1).expirationTime() == time_t(0)); time_t keyExpiration = key.subkey(0).expirationTime(); // change expiration of all subkeys ctx = Context::createForProtocol(key.protocol()); QVERIFY(!ctx->setExpire(key, 2000, std::vector(), Context::SetExpireAllSubkeys)); delete ctx; key.update(); QVERIFY (key.subkey(0).expirationTime() == keyExpiration); QVERIFY (key.subkey(1).expirationTime() != time_t(0)); time_t subkeyExpiration = key.subkey(1).expirationTime(); // change expiration of specific subkey(s) ctx = Context::createForProtocol(key.protocol()); std::vector specificSubkeys; specificSubkeys.push_back(key.subkey(1)); QVERIFY(!ctx->setExpire(key, 3000, specificSubkeys)); delete ctx; key.update(); QVERIFY (key.subkey(0).expirationTime() == keyExpiration); QVERIFY (key.subkey(1).expirationTime() != subkeyExpiration); + + // test error handling: calling setExpire() with the primary key as + // subkey should fail with "subkey not found" + ctx = Context::createForProtocol(key.protocol()); + std::vector primaryKey; + primaryKey.push_back(key.subkey(0)); + const auto err = ctx->setExpire(key, 3000, primaryKey); + QCOMPARE(err.code(), GPG_ERR_NOT_FOUND); + delete ctx; } void testVersion() { QVERIFY(EngineInfo::Version("2.1.0") < EngineInfo::Version("2.1.1")); QVERIFY(EngineInfo::Version("2.1.10") < EngineInfo::Version("2.1.11")); QVERIFY(EngineInfo::Version("2.2.0") > EngineInfo::Version("2.1.19")); QVERIFY(EngineInfo::Version("1.0.0") < EngineInfo::Version("2.0.0")); QVERIFY(EngineInfo::Version("0.1.0") < EngineInfo::Version("1.0.0")); QVERIFY(!(EngineInfo::Version("2.0.0") < EngineInfo::Version("2.0.0"))); QVERIFY(EngineInfo::Version("3.0.0") > EngineInfo::Version("2.3.20")); QVERIFY(EngineInfo::Version("3.0.1") > EngineInfo::Version("3.0.0")); QVERIFY(EngineInfo::Version("3.1.0") > EngineInfo::Version("3.0.20")); } void initTestCase() { QGpgMETest::initTestCase(); const QString gpgHome = qgetenv("GNUPGHOME"); QVERIFY(copyKeyrings(gpgHome, mDir.path())); qputenv("GNUPGHOME", mDir.path().toUtf8()); } private: QTemporaryDir mDir; }; QTEST_MAIN(TestVarious) #include "t-various.moc" diff --git a/src/Makefile.am b/src/Makefile.am index 1bbb5388..1061995c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,171 +1,172 @@ # Copyright (C) 2000 Werner Koch (dd9jn) # Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007 g10 Code GmbH # # This file is part of GPGME. # # GPGME is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 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 Lesser General # Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this program; if not, see . # SPDX-License-Identifier: LGPL-2.1-or-later ## Process this file with automake to produce Makefile.in pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = gpgme.pc gpgme-glib.pc EXTRA_DIST = gpgme-config.in gpgme.m4 libgpgme.vers ChangeLog-2011 \ gpgme.h.in versioninfo.rc.in gpgme.def \ gpgme.pc.in gpgme-glib.pc.in bin_SCRIPTS = gpgme-config m4datadir = $(datadir)/aclocal m4data_DATA = gpgme.m4 nodist_include_HEADERS = gpgme.h bin_PROGRAMS = gpgme-tool gpgme-json if BUILD_W32_GLIB ltlib_gpgme_glib = libgpgme-glib.la else ltlib_gpgme_glib = endif lib_LTLIBRARIES = libgpgme.la $(ltlib_gpgme_glib) if HAVE_LD_VERSION_SCRIPT libgpgme_version_script_cmd = -Wl,--version-script=$(srcdir)/libgpgme.vers else libgpgme_version_script_cmd = endif if HAVE_DOSISH_SYSTEM system_components = w32-util.c system_components_not_extra = w32-io.c else system_components = ath.h posix-util.c posix-io.c system_components_not_extra = endif if HAVE_UISERVER uiserver_components = engine-uiserver.c else uiserver_components = endif # These are the source files common to all library versions. We used # to build a non-installed library for that, but that does not work # correctly on all platforms (in particular, one can not specify the # right linking order with libtool, as the non-installed version has # unresolved symbols to the thread module. main_sources = \ util.h conversion.c b64dec.c get-env.c context.h ops.h \ parsetlv.c parsetlv.h \ mbox-util.c mbox-util.h \ data.h data.c data-fd.c data-stream.c data-mem.c data-user.c \ data-estream.c \ data-compat.c data-identify.c \ signers.c sig-notation.c \ wait.c wait-global.c wait-private.c wait-user.c wait.h \ op-support.c \ encrypt.c encrypt-sign.c decrypt.c decrypt-verify.c verify.c \ sign.c passphrase.c progress.c \ key.c keylist.c keysign.c trust-item.c trustlist.c tofupolicy.c \ import.c export.c genkey.c delete.c edit.c getauditlog.c \ + setexpire.c \ opassuan.c passwd.c spawn.c assuan-support.c \ engine.h engine-backend.h engine.c engine-gpg.c status-table.c \ engine-gpgsm.c engine-assuan.c engine-gpgconf.c \ $(uiserver_components) \ engine-g13.c vfs-mount.c vfs-create.c \ engine-spawn.c \ gpgconf.c queryswdb.c \ sema.h priv-io.h $(system_components) sys-util.h dirinfo.c \ debug.c debug.h gpgme.c version.c error.c \ ath.h ath.c libgpgme_la_SOURCES = $(main_sources) $(system_components_not_extra) if BUILD_W32_GLIB libgpgme_glib_la_SOURCES = $(main_sources) w32-glib-io.c endif # We use a global CFLAGS setting for all libraries # versions, because then every object file is only compiled once. AM_CFLAGS = @LIBASSUAN_CFLAGS@ @GLIB_CFLAGS@ gpgme_tool_SOURCES = gpgme-tool.c argparse.c argparse.h gpgme_tool_LDADD = libgpgme.la @LIBASSUAN_LIBS@ @GPG_ERROR_LIBS@ gpgme_json_SOURCES = gpgme-json.c cJSON.c cJSON.h gpgme_json_LDADD = -lm libgpgme.la $(GPG_ERROR_LIBS) if HAVE_W32_SYSTEM # Windows provides us with an endless stream of Tough Love. To spawn # processes with a controlled set of inherited handles, we need a # wrapper process. libexec_PROGRAMS = gpgme-w32spawn RCCOMPILE = $(RC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) LTRCCOMPILE = $(LIBTOOL) --mode=compile --tag=RC $(RCCOMPILE) SUFFIXES = .rc .lo .rc.lo: $(LTRCCOMPILE) -i "$<" -o "$@" gpgme_res = versioninfo.lo no_undefined = -no-undefined export_symbols = -export-symbols $(srcdir)/gpgme.def extra_ltoptions = -XCClinker -static-libgcc gpgme_w32_extra_libs = -lws2_32 install-def-file: -$(INSTALL) -d $(DESTDIR)$(libdir) $(INSTALL) $(srcdir)/gpgme.def $(DESTDIR)$(libdir)/gpgme.def uninstall-def-file: -rm $(DESTDIR)$(libdir)/gpgme.def gpgme_deps = $(gpgme_res) gpgme.def else gpgme_res = no_undefined = export_symbols = extra_ltoptions = install-def-file: uninstall-def-file: gpgme_deps = endif libgpgme_la_LDFLAGS = $(no_undefined) $(export_symbols) $(extra_ltoptions) \ $(libgpgme_version_script_cmd) -version-info \ @LIBGPGME_LT_CURRENT@:@LIBGPGME_LT_REVISION@:@LIBGPGME_LT_AGE@ libgpgme_la_DEPENDENCIES = @LTLIBOBJS@ $(srcdir)/libgpgme.vers $(gpgme_deps) libgpgme_la_LIBADD = $(gpgme_res) @LIBASSUAN_LIBS@ @LTLIBOBJS@ \ @GPG_ERROR_LIBS@ $(gpgme_w32_extra_libs) if BUILD_W32_GLIB libgpgme_glib_la_LDFLAGS = \ $(no_undefined) $(export_symbols) $(extra_ltoptions) \ $(libgpgme_version_script_cmd) -version-info \ @LIBGPGME_LT_CURRENT@:@LIBGPGME_LT_REVISION@:@LIBGPGME_LT_AGE@ libgpgme_glib_la_DEPENDENCIES = @LTLIBOBJS@ \ $(srcdir)/libgpgme.vers $(gpgme_deps) libgpgme_glib_la_LIBADD = $(gpgme_res) @LIBASSUAN_LIBS@ @LTLIBOBJS@ \ @GPG_ERROR_LIBS@ @GLIB_LIBS@ $(gpgme_w32_extra_libs) endif install-data-local: install-def-file uninstall-local: uninstall-def-file diff --git a/src/context.h b/src/context.h index 25dfc792..7f745a52 100644 --- a/src/context.h +++ b/src/context.h @@ -1,198 +1,198 @@ /* context.h - Definitions for a GPGME context. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 2002, 2003, 2004, 2005, 2010 g10 Code GmbH This file is part of GPGME. GPGME is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, see . */ #ifndef CONTEXT_H #define CONTEXT_H #include "gpgme.h" #include "engine.h" #include "wait.h" #include "sema.h" extern gpgme_error_t _gpgme_selftest; /* Operations might require to remember arbitrary information and data objects during invocations of the status handler. The ctx_op_data structure provides a generic framework to hook in such additional data. */ typedef enum { OPDATA_DECRYPT, OPDATA_SIGN, OPDATA_ENCRYPT, OPDATA_PASSPHRASE, OPDATA_IMPORT, OPDATA_GENKEY, OPDATA_KEYLIST, OPDATA_EDIT, OPDATA_VERIFY, OPDATA_TRUSTLIST, OPDATA_ASSUAN, OPDATA_VFS_MOUNT, OPDATA_PASSWD, OPDATA_EXPORT, OPDATA_KEYSIGN, OPDATA_TOFU_POLICY, - OPDATA_QUERY_SWDB + OPDATA_QUERY_SWDB, OPDATA_SETEXPIRE } ctx_op_data_id_t; /* "gpgmeres" in ASCII. */ #define CTX_OP_DATA_MAGIC 0x736572656d677067ULL struct ctx_op_data { /* A magic word just to make sure people don't deallocate something that ain't a result structure. */ unsigned long long magic; /* The next element in the linked list, or NULL if this is the last element. Used by op data structures linked into a context. */ struct ctx_op_data *next; /* The type of the hook data, which can be used by a routine to lookup the hook data. */ ctx_op_data_id_t type; /* The function to release HOOK and all its associated resources. Can be NULL if no special deallocation routine is necessary. */ void (*cleanup) (void *hook); /* The hook that points to the operation data. */ void *hook; /* The number of outstanding references. */ int references; }; typedef struct ctx_op_data *ctx_op_data_t; /* The context defines an environment in which crypto operations can be performed (sequentially). */ struct gpgme_context { DECLARE_LOCK (lock); /* True if the context was canceled asynchronously. */ int canceled; /* The engine info for this context. */ gpgme_engine_info_t engine_info; /* The protocol used by this context. */ gpgme_protocol_t protocol; /* The running engine process. */ engine_t engine; /* Engine's sub protocol. */ gpgme_protocol_t sub_protocol; /* True if armor mode should be used. */ unsigned int use_armor : 1; /* True if text mode should be used. */ unsigned int use_textmode : 1; /* True if offline mode should be used. */ unsigned int offline : 1; /* True if a status callback shall be called for nearly all status * lines. */ unsigned int full_status : 1; /* The Tofu info has a human readable string which is presented to * the user in a directly usable format. By enabling this flag the * unmodified string, as received form gpg, will be returned. */ unsigned int raw_description : 1; /* True if session keys should be exported upon decryption. */ unsigned int export_session_keys : 1; /* True if a Pinentry was launched during the last operation. This * flag is cleared with each operation. */ unsigned int redraw_suggested : 1; /* True if the option --include-key-block shall be passed to gpg. */ unsigned int include_key_block : 1; /* True if the option --auto-key-import shall be passed to gpg. */ unsigned int auto_key_import : 1; /* True if the option --auto-key-retrieve shall be passed to gpg. */ unsigned int auto_key_retrieve : 1; /* Do not use the symmtric encryption passphrase cache. */ unsigned int no_symkey_cache : 1; /* Pass --ignore-mdc-error to gpg. Note that this flag is reset * after the operation. */ unsigned int ignore_mdc_error : 1; /* Pass --expert to gpg edit key. */ unsigned int extended_edit : 1; /* Flags for keylist mode. */ gpgme_keylist_mode_t keylist_mode; /* The current pinentry mode. */ gpgme_pinentry_mode_t pinentry_mode; /* Number of certs to be included. */ unsigned int include_certs; /* The actual number of keys in SIGNERS, the allocated size of the * array, and the array with the signing keys. */ unsigned int signers_len; unsigned int signers_size; gpgme_key_t *signers; /* The signature notations for this context. */ gpgme_sig_notation_t sig_notations; /* The sender's addr-spec or NULL. */ char *sender; /* The gpg specific override session key or NULL. */ char *override_session_key; /* The optional request origin. */ char *request_origin; /* The optional auto key locate options. */ char *auto_key_locate; /* The locale for the pinentry. */ char *lc_ctype; char *lc_messages; /* The optional trust-model override. */ char *trust_model; /* The operation data hooked into the context. */ ctx_op_data_t op_data; /* The user provided passphrase callback and its hook value. */ gpgme_passphrase_cb_t passphrase_cb; void *passphrase_cb_value; /* The user provided progress callback and its hook value. */ gpgme_progress_cb_t progress_cb; void *progress_cb_value; /* The user provided status callback and its hook value. */ gpgme_status_cb_t status_cb; void *status_cb_value; /* A list of file descriptors in active use by the current operation. */ struct fd_table fdt; struct gpgme_io_cbs io_cbs; }; #endif /* CONTEXT_H */ diff --git a/src/genkey.c b/src/genkey.c index 981a0093..77576b18 100644 --- a/src/genkey.c +++ b/src/genkey.c @@ -1,717 +1,665 @@ /* genkey.c - Key generation. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2016 g10 Code GmbH * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include "gpgme.h" #include "debug.h" #include "context.h" #include "ops.h" #include "util.h" typedef struct { struct _gpgme_op_genkey_result result; /* The error code from a FAILURE status line or 0. */ gpg_error_t failure_code; /* The error code from certain ERROR status lines or 0. */ gpg_error_t error_code; /* Flag to indicate that a UID is to be added. */ gpg_error_t uidmode; /* The key parameters passed to the crypto engine. */ gpgme_data_t key_parameter; } *op_data_t; static void release_op_data (void *hook) { op_data_t opd = (op_data_t) hook; if (opd->result.fpr) free (opd->result.fpr); if (opd->key_parameter) gpgme_data_release (opd->key_parameter); } gpgme_genkey_result_t gpgme_op_genkey_result (gpgme_ctx_t ctx) { void *hook; op_data_t opd; gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_genkey_result", ctx, ""); err = _gpgme_op_data_lookup (ctx, OPDATA_GENKEY, &hook, -1, NULL); opd = hook; if (err || !opd) { TRACE_SUC ("result=(null)"); return NULL; } TRACE_LOG ("fpr = %s, %s, %s", opd->result.fpr, opd->result.primary ? "primary" : "no primary", opd->result.sub ? "sub" : "no sub"); TRACE_SUC ("result=%p", &opd->result); return &opd->result; } /* Parse an error status line. Return the error location and the error code. The function may modify ARGS. */ static char * parse_error (char *args, gpg_error_t *r_err) { char *where = strchr (args, ' '); char *which; if (where) { *where = '\0'; which = where + 1; where = strchr (which, ' '); if (where) *where = '\0'; where = args; } else { *r_err = trace_gpg_error (GPG_ERR_INV_ENGINE); return NULL; } *r_err = atoi (which); return where; } static gpgme_error_t genkey_status_handler (void *priv, gpgme_status_code_t code, char *args) { gpgme_ctx_t ctx = (gpgme_ctx_t) priv; gpgme_error_t err; void *hook; op_data_t opd; char *loc; /* Pipe the status code through the progress status handler. */ err = _gpgme_progress_status_handler (ctx, code, args); if (err) return err; err = _gpgme_op_data_lookup (ctx, OPDATA_GENKEY, &hook, -1, NULL); opd = hook; if (err) return err; switch (code) { case GPGME_STATUS_KEY_CREATED: if (args && *args) { if (*args == 'B' || *args == 'P') { opd->result.primary = 1; opd->result.uid = 1; } if (*args == 'B' || *args == 'S') opd->result.sub = 1; if (args[1] == ' ') { if (opd->result.fpr) free (opd->result.fpr); opd->result.fpr = strdup (&args[2]); if (!opd->result.fpr) return gpg_error_from_syserror (); } } break; case GPGME_STATUS_ERROR: loc = parse_error (args, &err); if (!loc) return err; if (!opd->error_code) opd->error_code = err; break; case GPGME_STATUS_FAILURE: opd->failure_code = _gpgme_parse_failure (args); break; case GPGME_STATUS_EOF: if (opd->error_code) return opd->error_code; else if (!opd->uidmode && !opd->result.primary && !opd->result.sub) return gpg_error (GPG_ERR_GENERAL); else if (opd->failure_code) return opd->failure_code; else if (opd->uidmode == 1) opd->result.uid = 1; /* We have no status line, thus this hack. */ break; case GPGME_STATUS_INQUIRE_MAXLEN: if (ctx->status_cb && !ctx->full_status) { err = ctx->status_cb (ctx->status_cb_value, "INQUIRE_MAXLEN", args); if (err) return err; } break; default: break; } return 0; } static gpgme_error_t get_key_parameter (const char *parms, gpgme_data_t *key_parameter) { const char *content; const char *attrib; const char *endtag; /* Extract the key parameter from the XML structure. */ parms = strstr (parms, "'); if (!content) return gpg_error (GPG_ERR_INV_VALUE); content++; attrib = strstr (parms, "format=\"internal\""); if (!attrib || attrib >= content) return gpg_error (GPG_ERR_INV_VALUE); endtag = strstr (content, ""); if (!endtag) endtag = content + strlen (content); /* FIXME: Check that there are no control statements inside. */ while (content < endtag && (content[0] == '\n' || (content[0] == '\r' && content[1] == '\n'))) content++; return gpgme_data_new_from_mem (key_parameter, content, endtag - content, 1); } static gpgme_error_t genkey_start (gpgme_ctx_t ctx, int synchronous, const char *parms, gpgme_data_t pubkey, gpgme_data_t seckey) { gpgme_error_t err; void *hook; op_data_t opd; err = _gpgme_op_reset (ctx, synchronous); if (err) return err; err = _gpgme_op_data_lookup (ctx, OPDATA_GENKEY, &hook, sizeof (*opd), release_op_data); opd = hook; if (err) return err; err = get_key_parameter (parms, &opd->key_parameter); if (err) return err; _gpgme_engine_set_status_handler (ctx->engine, genkey_status_handler, ctx); if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } return _gpgme_engine_op_genkey (ctx->engine, NULL, NULL, 0, 0, NULL, 0, opd->key_parameter, ctx->use_armor? GENKEY_EXTRAFLAG_ARMOR:0, pubkey, seckey); } /* Generate a new keypair and add it to the keyring. PUBKEY and SECKEY should be null for now. PARMS specifies what keys should be generated. */ gpgme_error_t gpgme_op_genkey_start (gpgme_ctx_t ctx, const char *parms, gpgme_data_t pubkey, gpgme_data_t seckey) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_genkey_start", ctx, "pubkey=%p, seckey=%p", pubkey, seckey); TRACE_LOGBUF (parms, parms? strlen (parms):0); if (!ctx || !parms) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); err = genkey_start (ctx, 0, parms, pubkey, seckey); return TRACE_ERR (err); } /* Generate a new keypair and add it to the keyring. PUBKEY and SECKEY should be null for now. PARMS specifies what keys should be generated. */ gpgme_error_t gpgme_op_genkey (gpgme_ctx_t ctx, const char *parms, gpgme_data_t pubkey, gpgme_data_t seckey) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_genkey", ctx, "pubkey=%p, seckey=%p", pubkey, seckey); TRACE_LOGBUF (parms, parms? strlen (parms):0); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); err = genkey_start (ctx, 1, parms, pubkey, seckey); if (!err) err = _gpgme_wait_one (ctx); return TRACE_ERR (err); } static gpgme_error_t createkey_start (gpgme_ctx_t ctx, int synchronous, const char *userid, const char *algo, unsigned long reserved, unsigned long expires, gpgme_key_t anchorkey, unsigned int flags) { gpgme_error_t err; void *hook; op_data_t opd; err = _gpgme_op_reset (ctx, synchronous); if (err) return err; if (reserved || anchorkey || !userid) return gpg_error (GPG_ERR_INV_ARG); err = _gpgme_op_data_lookup (ctx, OPDATA_GENKEY, &hook, sizeof (*opd), release_op_data); opd = hook; if (err) return err; _gpgme_engine_set_status_handler (ctx->engine, genkey_status_handler, ctx); if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } return _gpgme_engine_op_genkey (ctx->engine, userid, algo, reserved, expires, anchorkey, flags, NULL, ctx->use_armor? GENKEY_EXTRAFLAG_ARMOR:0, NULL, NULL); } gpgme_error_t gpgme_op_createkey_start (gpgme_ctx_t ctx, const char *userid, const char *algo, unsigned long reserved, unsigned long expires, gpgme_key_t anchorkey, unsigned int flags) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_createkey_start", ctx, "userid='%s', algo='%s' flags=0x%x", userid, algo, flags); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); err = createkey_start (ctx, 0, userid, algo, reserved, expires, anchorkey, flags); return TRACE_ERR (err); } gpgme_error_t gpgme_op_createkey (gpgme_ctx_t ctx, const char *userid, const char *algo, unsigned long reserved, unsigned long expires, gpgme_key_t anchorkey, unsigned int flags) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_createkey", ctx, "userid='%s', algo='%s' flags=0x%x", userid, algo, flags); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); err = createkey_start (ctx, 1, userid, algo, reserved, expires, anchorkey, flags); if (!err) err = _gpgme_wait_one (ctx); return TRACE_ERR (err); } static gpgme_error_t createsubkey_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t key, const char *algo, unsigned long reserved, unsigned long expires, unsigned int flags) { gpgme_error_t err; void *hook; op_data_t opd; if (ctx->protocol != GPGME_PROTOCOL_OPENPGP) return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL); err = _gpgme_op_reset (ctx, synchronous); if (err) return err; if (reserved || !key) return gpg_error (GPG_ERR_INV_ARG); err = _gpgme_op_data_lookup (ctx, OPDATA_GENKEY, &hook, sizeof (*opd), release_op_data); opd = hook; if (err) return err; _gpgme_engine_set_status_handler (ctx->engine, genkey_status_handler, ctx); if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } return _gpgme_engine_op_genkey (ctx->engine, NULL, algo, reserved, expires, key, flags, NULL, ctx->use_armor? GENKEY_EXTRAFLAG_ARMOR:0, NULL, NULL); } /* Add a subkey to an existing KEY. */ gpgme_error_t gpgme_op_createsubkey_start (gpgme_ctx_t ctx, gpgme_key_t key, const char *algo, unsigned long reserved, unsigned long expires, unsigned int flags) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_createsubkey_start", ctx, "key=%p, algo='%s' flags=0x%x", key, algo, flags); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); err = createsubkey_start (ctx, 0, key, algo, reserved, expires, flags); return TRACE_ERR (err); } gpgme_error_t gpgme_op_createsubkey (gpgme_ctx_t ctx, gpgme_key_t key, const char *algo, unsigned long reserved, unsigned long expires, unsigned int flags) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_createsubkey", ctx, "key=%p, algo='%s' flags=0x%x", key, algo, flags); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); err = createsubkey_start (ctx, 1, key, algo, reserved, expires, flags); if (!err) err = _gpgme_wait_one (ctx); return TRACE_ERR (err); } static gpgme_error_t addrevuid_start (gpgme_ctx_t ctx, int synchronous, int extraflags, gpgme_key_t key, const char *userid, unsigned int flags) { gpgme_error_t err; void *hook; op_data_t opd; if (ctx->protocol != GPGME_PROTOCOL_OPENPGP) return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL); if (!key || !userid) return gpg_error (GPG_ERR_INV_ARG); err = _gpgme_op_reset (ctx, synchronous); if (err) return err; err = _gpgme_op_data_lookup (ctx, OPDATA_GENKEY, &hook, sizeof (*opd), release_op_data); opd = hook; if (err) return err; opd->uidmode = extraflags? 2 : 1; _gpgme_engine_set_status_handler (ctx->engine, genkey_status_handler, ctx); if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler (ctx->engine, _gpgme_passphrase_command_handler, ctx); if (err) return err; } return _gpgme_engine_op_genkey (ctx->engine, userid, NULL, 0, 0, key, flags, NULL, extraflags, NULL, NULL); } /* Add USERID to an existing KEY. */ gpgme_error_t gpgme_op_adduid_start (gpgme_ctx_t ctx, gpgme_key_t key, const char *userid, unsigned int flags) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_adduid_start", ctx, "uid='%s' flags=0x%x", userid, flags); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); err = addrevuid_start (ctx, 0, 0, key, userid, flags); return TRACE_ERR (err); } gpgme_error_t gpgme_op_adduid (gpgme_ctx_t ctx, gpgme_key_t key, const char *userid, unsigned int flags) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_adduid", ctx, "uid='%s' flags=0x%x", userid, flags); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); err = addrevuid_start (ctx, 1, 0, key, userid, flags); if (!err) err = _gpgme_wait_one (ctx); return TRACE_ERR (err); } /* Revoke USERID from KEY. */ gpgme_error_t gpgme_op_revuid_start (gpgme_ctx_t ctx, gpgme_key_t key, const char *userid, unsigned int flags) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_revuid_start", ctx, "uid='%s' flags=0x%x", userid, flags); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); err = addrevuid_start (ctx, 0, GENKEY_EXTRAFLAG_REVOKE, key, userid, flags); return TRACE_ERR (err); } gpgme_error_t gpgme_op_revuid (gpgme_ctx_t ctx, gpgme_key_t key, const char *userid, unsigned int flags) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_revuid", ctx, "uid='%s' flags=0x%x", userid, flags); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); err = addrevuid_start (ctx, 1, GENKEY_EXTRAFLAG_REVOKE, key, userid, flags); if (!err) err = _gpgme_wait_one (ctx); return TRACE_ERR (err); } /* Set a flag on the USERID of KEY. The only supported flag right now * is "primary" to mark the primary key. */ static gpg_error_t set_uid_flag (gpgme_ctx_t ctx, int synchronous, gpgme_key_t key, const char *userid, const char *name, const char *value) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_set_uid_flag", ctx, "%d uid='%s' '%s'='%s'", synchronous, userid, name, value); if (!ctx || !name || !key || !userid) return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); if (!strcmp (name, "primary")) { if (value) err = gpg_error (GPG_ERR_INV_ARG); else err = addrevuid_start (ctx, synchronous, GENKEY_EXTRAFLAG_SETPRIMARY, key, userid, 0); } else return err = gpg_error (GPG_ERR_UNKNOWN_NAME); if (synchronous && !err) err = _gpgme_wait_one (ctx); return TRACE_ERR (err); } /* See set_uid_flag. */ gpgme_error_t gpgme_op_set_uid_flag_start (gpgme_ctx_t ctx, gpgme_key_t key, const char *userid, const char *name, const char *value) { return set_uid_flag (ctx, 0, key, userid, name, value); } /* See set_uid_flag. This is the synchronous variant. */ gpgme_error_t gpgme_op_set_uid_flag (gpgme_ctx_t ctx, gpgme_key_t key, const char *userid, const char *name, const char *value) { return set_uid_flag (ctx, 1, key, userid, name, value); } - -/* Set the expiration time of a key or its subkeys. See - --quick-set-expire in the gnupg documentation. */ -static gpg_error_t -setexpire (gpgme_ctx_t ctx, int synchronous, - gpgme_key_t key, - unsigned long expires, - const char *subfprs, - unsigned int reserved) -{ - gpgme_error_t err = 0; - - TRACE_BEG (DEBUG_CTX, "gpgme_op_setexpire", ctx, - "%d key=%p expiry: %lu subkeys: '%s' reserved=0x%x", - synchronous, key, expires, subfprs, reserved); - - if (!ctx || !key) - return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG)); - - err = _gpgme_op_reset (ctx, synchronous); - if (err) - return err; - - err = _gpgme_engine_op_setexpire (ctx->engine, key, expires, subfprs, reserved); - - if (synchronous && !err) - err = _gpgme_wait_one (ctx); - return TRACE_ERR (err); -} - -/* See setexpire. */ -gpgme_error_t -gpgme_op_setexpire_start (gpgme_ctx_t ctx, - gpgme_key_t key, - unsigned long expires, - const char *subfprs, - unsigned int reserved) -{ - return setexpire (ctx, 0, key, expires, subfprs, reserved); -} - - -/* See setexpire. This is the synchronous variant. */ -gpgme_error_t -gpgme_op_setexpire (gpgme_ctx_t ctx, - gpgme_key_t key, - unsigned long expires, - const char *subfprs, - unsigned int reserved) -{ - return setexpire (ctx, 1, key, expires, subfprs, reserved); -} diff --git a/src/setexpire.c b/src/setexpire.c new file mode 100644 index 00000000..5161499a --- /dev/null +++ b/src/setexpire.c @@ -0,0 +1,193 @@ +/* setexpire.c - Set expire helpers. + * Copyright (C) 2020 g10 Code GmbH + * + * This file is part of GPGME. + * + * GPGME is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see . + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#if HAVE_CONFIG_H +#include +#endif +#include + +#include "gpgme.h" +#include "debug.h" +#include "context.h" +#include "ops.h" + + +typedef struct +{ + /* The error code from a FAILURE status line or 0. */ + gpg_error_t failure_code; + + /* The error code from an ERROR status line or 0. */ + gpg_error_t error_code; + +} *op_data_t; + + +/* Parse an error status line. Return the error location and the + error code. The function may modify ARGS. */ +static char * +parse_error (char *args, gpg_error_t *r_err) +{ + char *where = strchr (args, ' '); + char *which; + + if (where) + { + *where = '\0'; + which = where + 1; + + where = strchr (which, ' '); + if (where) + *where = '\0'; + + where = args; + } + else + { + *r_err = trace_gpg_error (GPG_ERR_INV_ENGINE); + return NULL; + } + + *r_err = atoi (which); + + return where; +} + + +static gpgme_error_t +setexpire_status_handler (void *priv, gpgme_status_code_t code, char *args) +{ + gpgme_ctx_t ctx = (gpgme_ctx_t) priv; + gpgme_error_t err; + void *hook; + op_data_t opd; + char *loc; + + err = _gpgme_op_data_lookup (ctx, OPDATA_SETEXPIRE, &hook, -1, NULL); + opd = hook; + if (err) + return err; + + switch (code) + { + case GPGME_STATUS_ERROR: + loc = parse_error (args, &err); + if (!loc) + return err; + if (!opd->error_code) + opd->error_code = err; + break; + + case GPGME_STATUS_FAILURE: + opd->failure_code = _gpgme_parse_failure (args); + break; + + case GPGME_STATUS_EOF: + if (opd->error_code) + err = opd->error_code; + else if (opd->failure_code) + err = opd->failure_code; + break; + + default: + break; + } + + return err; +} + + +/* Set the expiration time of a key or its subkeys. See + --quick-set-expire in the gnupg documentation. */ +static gpg_error_t +setexpire (gpgme_ctx_t ctx, int synchronous, + gpgme_key_t key, + unsigned long expires, + const char *subfprs, + unsigned int reserved) +{ + gpgme_error_t err; + void *hook; + op_data_t opd; + + TRACE_BEG (DEBUG_CTX, "gpgme_op_setexpire", ctx, + "%d key=%p expiry: %lu subkeys: '%s' reserved=0x%x", + synchronous, key, expires, subfprs, reserved); + + if (!ctx) + return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); + + if (ctx->protocol != GPGME_PROTOCOL_OPENPGP) + return TRACE_ERR (gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL)); + + if (!key) + return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); + + err = _gpgme_op_reset (ctx, synchronous); + if (err) + return err; + + err = _gpgme_op_data_lookup (ctx, OPDATA_SETEXPIRE, &hook, sizeof (*opd), + NULL); + opd = hook; + if (err) + return err; + + _gpgme_engine_set_status_handler (ctx->engine, setexpire_status_handler, + ctx); + + if (ctx->passphrase_cb) + { + err = _gpgme_engine_set_command_handler + (ctx->engine, _gpgme_passphrase_command_handler, ctx); + if (err) + return err; + } + + err = _gpgme_engine_op_setexpire (ctx->engine, key, expires, subfprs, reserved); + + if (synchronous && !err) + err = _gpgme_wait_one (ctx); + return TRACE_ERR (err); +} + + +/* See setexpire. */ +gpgme_error_t +gpgme_op_setexpire_start (gpgme_ctx_t ctx, + gpgme_key_t key, + unsigned long expires, + const char *subfprs, + unsigned int reserved) +{ + return setexpire (ctx, 0, key, expires, subfprs, reserved); +} + + +/* See setexpire. This is the synchronous variant. */ +gpgme_error_t +gpgme_op_setexpire (gpgme_ctx_t ctx, + gpgme_key_t key, + unsigned long expires, + const char *subfprs, + unsigned int reserved) +{ + return setexpire (ctx, 1, key, expires, subfprs, reserved); +}