core: Allow specifying an expiration date for key signatures

* src/context.h (struct gpgme_context): Add 'cert_expire'.
* src/engine-gpg.c (append_args_from_cert_expire): New.
(gpg_edit): Set option according to the new flag.
* src/gpgme.c (gpgme_release): Free 'cert_expire'.
(gpgme_set_ctx_flag, gpgme_get_ctx_flag): Add "cert-expire".

* tests/gpg/Makefile.am (c_tests): Add new test.
(gpg.conf): Write "allow-weak-key-signatures" to gpg.conf.
* tests/gpg/t-edit-sign.c: New.
--

The new context flag "cert-expire" allows setting the expiration date
for key signatures created with gpgme_op_interact.

GnuPG-bug-id: 5336, 5505
This commit is contained in:
Ingo Klöcker 2021-06-22 16:23:26 +02:00
parent ab1d4ef580
commit 34d9defc42
7 changed files with 253 additions and 1 deletions

3
NEWS
View File

@ -1,12 +1,15 @@
Noteworthy changes in version 1.15.2 (unreleased)
-------------------------------------------------
* New context flag "cert-expire".
* cpp, qt: Add support for trust signatures. [#5421]
* qt: Add support for flags in LDAP server options. [#5217]
* Interface changes relative to the 1.15.1 release:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gpgme_set_ctx_flag EXTENDED: New flag 'cert-expire'.
cpp: SignKeyJob::setTrustSignature NEW.
cpp: TrustSignatureTrust NEW.
cpp: GpgSignKeyEditInteractor::setTrustSignatureTrust NEW.

View File

@ -3179,6 +3179,14 @@ rebuilding the trust-db.
This flag passes the option @option{--expert} to gpg key edit. This
can be used to get additional callbacks in @code{gpgme_op_edit}.
@item "cert-expire"
@since{1.15.2}
The string given in @var{value} is passed to the GnuPG engine to set
the expiration time to use for key signature expiration. Valid values
are documented in the GnuPG manual and the gpg man page under
the option @option{--default-cert-expire}.
@end table
This function returns @code{0} on success.

View File

@ -174,6 +174,9 @@ struct gpgme_context
/* The optional trust-model override. */
char *trust_model;
/* The optional expiration date of a certification. */
char *cert_expire;
/* The operation data hooked into the context. */
ctx_op_data_t op_data;

View File

@ -1960,6 +1960,27 @@ append_args_from_sig_notations (engine_gpg_t gpg, gpgme_ctx_t ctx /* FIXME */,
}
static gpgme_error_t
append_args_from_cert_expire (engine_gpg_t gpg, gpgme_ctx_t ctx)
{
gpgme_error_t err;
if (ctx->cert_expire)
{
/* Override ask-cert-expire set in the configuration, so that the specified
* default is actually used. */
err = add_arg (gpg, "--no-ask-cert-expire");
if (!err)
err = add_arg (gpg, "--default-cert-expire");
if (!err)
err = add_arg (gpg, ctx->cert_expire);
}
else
err = 0;
return err;
}
static gpgme_error_t
gpg_edit (void *engine, int type, gpgme_key_t key, gpgme_data_t out,
gpgme_ctx_t ctx /* FIXME */)
@ -1975,6 +1996,8 @@ gpg_edit (void *engine, int type, gpgme_key_t key, gpgme_data_t out,
err = append_args_from_signers (gpg, ctx);
if (!err)
err = append_args_from_sig_notations (gpg, ctx, NOTATION_FLAG_CERT);
if (!err)
err = append_args_from_cert_expire (gpg, ctx);
if (!err)
err = add_arg (gpg, type == 0 ? "--edit-key" : "--card-edit");
if (!err)

View File

@ -253,6 +253,7 @@ gpgme_release (gpgme_ctx_t ctx)
free (ctx->request_origin);
free (ctx->auto_key_locate);
free (ctx->trust_model);
free (ctx->cert_expire);
_gpgme_engine_info_release (ctx->engine_info);
ctx->engine_info = NULL;
DESTROY_LOCK (ctx->lock);
@ -578,6 +579,13 @@ gpgme_set_ctx_flag (gpgme_ctx_t ctx, const char *name, const char *value)
{
ctx->extended_edit = abool;
}
else if (!strcmp (name, "cert-expire"))
{
free (ctx->cert_expire);
ctx->cert_expire = strdup (value);
if (!ctx->cert_expire)
err = gpg_error_from_syserror ();
}
else
err = gpg_error (GPG_ERR_UNKNOWN_NAME);
@ -647,6 +655,10 @@ gpgme_get_ctx_flag (gpgme_ctx_t ctx, const char *name)
{
return ctx->extended_edit ? "1":"";
}
else if (!strcmp (name, "cert-expire"))
{
return ctx->cert_expire? ctx->cert_expire : "";
}
else
return NULL;
}

View File

@ -39,7 +39,7 @@ c_tests = \
t-encrypt t-encrypt-sym t-encrypt-sign t-sign t-signers \
t-decrypt t-verify t-decrypt-verify t-sig-notation t-export \
t-import t-edit t-keylist t-keylist-sig t-keylist-secret-sig t-wait \
t-encrypt-large t-file-name t-gpgconf t-encrypt-mixed \
t-encrypt-large t-file-name t-gpgconf t-encrypt-mixed t-edit-sign \
$(tests_unix)
TESTS = initial.test $(c_tests) final.test
@ -104,6 +104,8 @@ pubring-stamp: $(srcdir)/pubdemo.asc gpg-sample.stamp
gpg.conf:
# This is required for t-sig-notations.
echo no-force-v3-sigs > ./gpg.conf
# This is required for t-edit-sign.
echo allow-weak-key-signatures >> ./gpg.conf
gpg-agent.conf:
# This is required for gpg2, which does not support command fd for the

201
tests/gpg/t-edit-sign.c Normal file
View File

@ -0,0 +1,201 @@
/* t-edit-sign.c - Regression test.
* Copyright (C) 2000 Werner Koch (dd9jn)
* Copyright (C) 2001, 2002, 2003, 2004, 2021 g10 Code GmbH
* Software engineering by Ingo Klöcker <dev@ingo-kloecker.de>
*
* 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 <https://gnu.org/licenses/>.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
/* We need to include config.h so that we know whether we are building
with large file system (LFS) support. */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <errno.h>
#include <gpgme.h>
#include "t-support.h"
static void
flush_data (gpgme_data_t dh)
{
char buf[100];
int ret;
ret = gpgme_data_seek (dh, 0, SEEK_SET);
if (ret)
fail_if_err (gpgme_error_from_errno (errno));
while ((ret = gpgme_data_read (dh, buf, 100)) > 0)
fwrite (buf, ret, 1, stdout);
if (ret < 0)
fail_if_err (gpgme_error_from_errno (errno));
}
gpgme_error_t
interact_fnc (void *opaque, const char *status, const char *args, int fd)
{
const char *result = NULL;
gpgme_data_t out = (gpgme_data_t) opaque;
fputs ("[-- Response --]\n", stdout);
flush_data (out);
fprintf (stdout, "[-- Code: %s, %s --]\n", status, args);
if (fd >= 0)
{
if (!strcmp (args, "keyedit.prompt"))
{
static int step = 0;
switch (step)
{
case 0:
result = "fpr";
break;
case 1:
/* This fixes the primary user ID so the keylisting
tests will have predictable output. */
result = "1";
break;
case 2:
result = "sign";
break;
default:
result = "quit";
break;
}
step++;
}
else if (!strcmp (args, "keyedit.save.okay"))
result = "Y";
else if (!strcmp (args, "sign_uid.okay"))
result = "Y";
}
if (result)
{
gpgme_io_writen (fd, result, strlen (result));
gpgme_io_writen (fd, "\n", 1);
}
return 0;
}
int
main (int argc, char **argv)
{
gpgme_ctx_t ctx;
gpgme_error_t err;
gpgme_data_t out = NULL;
const char *signer_fpr = "A0FF4590BB6122EDEF6E3C542D727CC768697734"; /* Alpha Test */
gpgme_key_t signing_key = NULL;
const char *key_fpr = "D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2"; /* Bravo Test */
gpgme_key_t key = NULL;
gpgme_key_t signed_key = NULL;
gpgme_user_id_t signed_uid = NULL;
gpgme_key_sig_t key_sig = NULL;
char *agent_info;
int mode;
(void)argc;
(void)argv;
init_gpgme (GPGME_PROTOCOL_OpenPGP);
err = gpgme_new (&ctx);
fail_if_err (err);
/* Sign the key */
agent_info = getenv("GPG_AGENT_INFO");
if (!(agent_info && strchr (agent_info, ':')))
gpgme_set_passphrase_cb (ctx, passphrase_cb, 0);
err = gpgme_get_key (ctx, signer_fpr, &signing_key, 1);
fail_if_err (err);
err = gpgme_signers_add (ctx, signing_key);
fail_if_err (err);
err = gpgme_set_ctx_flag (ctx, "cert-expire", "42d");
fail_if_err (err);
err = gpgme_get_key (ctx, key_fpr, &key, 0);
fail_if_err (err);
err = gpgme_data_new (&out);
fail_if_err (err);
err = gpgme_op_interact (ctx, key, 0, interact_fnc, out, out);
fail_if_err (err);
fputs ("[-- Last response --]\n", stdout);
flush_data (out);
gpgme_data_release (out);
gpgme_key_unref (key);
gpgme_key_unref (signing_key);
/* Verify the key signature */
mode = gpgme_get_keylist_mode (ctx);
mode |= GPGME_KEYLIST_MODE_SIGS;
err = gpgme_set_keylist_mode (ctx, mode);
fail_if_err (err);
err = gpgme_get_key (ctx, key_fpr, &signed_key, 0);
fail_if_err (err);
signed_uid = key->uids;
if (!signed_uid)
{
fprintf (stderr, "Signed key has no user IDs\n");
exit (1);
}
if (!signed_uid->signatures || !signed_uid->signatures->next)
{
fprintf (stderr, "Signed user ID has less signatures than expected\n");
exit (1);
}
key_sig = signed_uid->signatures->next;
if (strcmp ("2D727CC768697734", key_sig->keyid))
{
fprintf (stderr, "Unexpected key ID in second user ID sig: %s\n",
key_sig->keyid);
exit (1);
}
if (key_sig->expires != key_sig->timestamp + 42*86400L)
{
fprintf (stderr, "Key signature unexpectedly does not expire in 42 days\n");
fprintf (stderr, "signature date: %ld, expiration date: %ld\n",
key_sig->timestamp, key_sig->expires);
exit (1);
}
gpgme_key_unref (signed_key);
gpgme_release (ctx);
return 0;
}