From 3d3b941ce9fd5def16587a3c1688ea86222695be Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 3 Apr 2023 12:01:37 +0200 Subject: [PATCH] gpgsm: Create binary detached sigs with definite form length octets. * sm/sign.c: Include tlv.h. (write_detached_signature): New, (gpgsm_sign): Fixup binary detached signatures. -- This helps some other software to verify detached signatures. (cherry picked from commit 8996b0b655952fa6b5bb678a92d3106f72f80f2a) --- NEWS | 7 ++ sm/sign.c | 254 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 257 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 4a682b5be..1b617e431 100644 --- a/NEWS +++ b/NEWS @@ -31,6 +31,13 @@ Noteworthy changes in version 2.2.42 (unreleased) * gpgsm: Major rewrite of the PKCS#12 parser. [T6536] + * gpgsm: Non-armored detached signature are now created without + using indefinite form length octets. This improves compatibility + with some PDF signature verification software. + + * gpgsm: Verification of detached signatures does now strip trailing + zeroes from the input if --assume-binary is used. [rG6bdf11f671] + * dirmngr: New option --ignore-crl-extensions. [T6545] * dirmngr: Backport of the AD_QUERY command. [rG2a3bad5985] diff --git a/sm/sign.c b/sm/sign.c index d6789fb94..83d44ebba 100644 --- a/sm/sign.c +++ b/sm/sign.c @@ -1,6 +1,8 @@ /* sign.c - Sign a message * Copyright (C) 2001, 2002, 2003, 2008, * 2010 Free Software Foundation, Inc. + * Copyright (C) 2003-2012, 2016-2017, 2019, + * 2020, 2022-2023 g10 Code GmbH * * This file is part of GnuPG. * @@ -16,6 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, see . + * SPDX-License-Identifier: GPL-3.0-or-later */ #include @@ -33,6 +36,7 @@ #include "keydb.h" #include "../common/i18n.h" +#include "../common/tlv.h" /* Hash the data and return if something was hashed. Return -1 on error. */ @@ -303,6 +307,215 @@ add_certificate_list (ctrl_t ctrl, ksba_cms_t cms, ksba_cert_t cert) + +/* This function takes a binary detached signature in (BLOB,BLOBLEN) + * and writes it to OUT_FP. The core of the function is to replace + * NDEF length sequences in the input to those with fixed inputs. + * This helps certain other implementations to properly verify + * detached signature. Moreover, it allows our own trailing zero + * stripping code - which we need for PDF signatures - to work + * correctly. + * + * Example start of a detached signature as created by us: + * 0 NDEF: SEQUENCE { -- 1st sequence + * 2 9: OBJECT IDENTIFIER signedData (1 2 840 113549 1 7 2) + * 13 NDEF: [0] { -- 2nd sequence + * 15 NDEF: SEQUENCE { -- 3rd sequence + * 17 1: INTEGER 1 -- version + * 20 15: SET { -- set of algorithms + * 22 13: SEQUENCE { + * 24 9: OBJECT IDENTIFIER sha-256 (2 16 840 1 101 3 4 2 1) + * 35 0: NULL + * : } + * : } + * 37 NDEF: SEQUENCE { -- 4th pretty short sequence + * 39 9: OBJECT IDENTIFIER data (1 2 840 113549 1 7 1) + * : } + * 52 869: [0] { + * Our goal is to replace the NDEF by fixed length tags. + */ +static gpg_error_t +write_detached_signature (const void *blob, size_t bloblen, estream_t out_fp) +{ + gpg_error_t err; + const unsigned char *p; + size_t n, objlen, hdrlen; + int class, tag, cons, ndef; + const unsigned char *p_ctoid, *p_version, *p_algoset, *p_dataoid; + size_t n_ctoid, n_version, n_algoset, n_dataoid; + const unsigned char *p_certset, *p_signerinfos; + size_t n_certset, n_signerinfos; + int i; + ksba_der_t dbld; + unsigned char *finalder = NULL; + size_t finalderlen; + + p = blob; + n = bloblen; + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No 1st sequence. */ + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !cons)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No signedData OID. */ + if (objlen > n) + return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ + p_ctoid = p; + n_ctoid = objlen; + p += objlen; + n -= objlen; + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_CONTEXT && tag == 0 && cons)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No 2nd sequence. */ + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No 3rd sequence. */ + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_INTEGER)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No version. */ + if (objlen > n) + return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ + p_version = p; + n_version = objlen; + p += objlen; + n -= objlen; + + p_algoset = p; + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_SET && cons && !ndef)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No set of algorithms. */ + if (objlen > n) + return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ + n_algoset = hdrlen + objlen; + p += objlen; + n -= objlen; + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No 4th sequence. */ + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !cons)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No data OID. */ + if (objlen > n) + return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ + p_dataoid = p; + n_dataoid = objlen; + p += objlen; + n -= objlen; + + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_NONE && !cons && !objlen)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No End tag. */ + + /* certificates [0] IMPLICIT CertificateSet OPTIONAL, + * Note: We ignore the following + * crls [1] IMPLICIT CertificateRevocationLists OPTIONAL + * because gpgsm does not create them. */ + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))) + return err; + if (class == CLASS_CONTEXT && tag == 0 && cons) + { + if (objlen > n) + return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ + p_certset = p; + n_certset = objlen; + p += objlen; + n -= objlen; + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef, + &objlen,&hdrlen))) + return err; + } + else + { + p_certset = NULL; + n_certset = 0; + } + + /* SignerInfos ::= SET OF SignerInfo */ + if (!(class == CLASS_UNIVERSAL && tag == TAG_SET && cons && !ndef)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No set of signerInfos. */ + if (objlen > n) + return gpg_error (GPG_ERR_BAD_BER); /* Object larger than data. */ + p_signerinfos = p; + n_signerinfos = objlen; + p += objlen; + n -= objlen; + + /* For the fun of it check the 3 end tags. */ + for (i=0; i < 3; i++) + { + if ((err=parse_ber_header (&p,&n,&class,&tag,&cons,&ndef, + &objlen,&hdrlen))) + return err; + if (!(class == CLASS_UNIVERSAL && tag == TAG_NONE && !cons && !objlen)) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* No End tag. */ + } + if (n) + return gpg_error (GPG_ERR_INV_CMS_OBJ); /* Garbage */ + + /*---- From here on we jump to leave on error. ----*/ + + /* Now create a new object from the collected data. */ + dbld = ksba_der_builder_new (16); /* (pre-allocate 16 items) */ + if (!dbld) + { + err = gpg_error_from_syserror (); + goto leave; + } + ksba_der_add_tag (dbld, 0, KSBA_TYPE_SEQUENCE); + ksba_der_add_val ( dbld, 0, KSBA_TYPE_OBJECT_ID, p_ctoid, n_ctoid); + ksba_der_add_tag ( dbld, KSBA_CLASS_CONTEXT, 0); + ksba_der_add_tag ( dbld, 0, KSBA_TYPE_SEQUENCE); + ksba_der_add_val ( dbld, 0, KSBA_TYPE_INTEGER, p_version, n_version); + ksba_der_add_der ( dbld, p_algoset, n_algoset); + ksba_der_add_tag ( dbld, 0, KSBA_TYPE_SEQUENCE); + ksba_der_add_val ( dbld, 0, KSBA_TYPE_OBJECT_ID, p_dataoid, n_dataoid); + ksba_der_add_end ( dbld); + if (p_certset) + { + ksba_der_add_tag ( dbld, KSBA_CLASS_CONTEXT, 0); + ksba_der_add_der ( dbld, p_certset, n_certset); + ksba_der_add_end ( dbld); + } + ksba_der_add_tag ( dbld, 0, KSBA_TYPE_SET); + ksba_der_add_der ( dbld, p_signerinfos, n_signerinfos); + ksba_der_add_end ( dbld); + ksba_der_add_end ( dbld); + ksba_der_add_end ( dbld); + ksba_der_add_end (dbld); + + err = ksba_der_builder_get (dbld, &finalder, &finalderlen); + if (err) + goto leave; + + if (es_fwrite (finalder, finalderlen, 1, out_fp) != 1) + { + err = gpg_error_from_syserror (); + goto leave; + } + + + leave: + ksba_der_release (dbld); + ksba_free (finalder); + return err; +} + + /* Perform a sign operation. @@ -318,6 +531,7 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, gpg_error_t err; gnupg_ksba_io_t b64writer = NULL; ksba_writer_t writer; + estream_t sig_fp = NULL; /* Used for detached signatures. */ ksba_cms_t cms = NULL; ksba_stop_reason_t stopreason; KEYDB_HANDLE kh = NULL; @@ -328,6 +542,7 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, ksba_isotime_t signed_at; certlist_t cl; int release_signerlist = 0; + int binary_detached = detached && !ctrl->create_pem && !ctrl->create_base64; audit_set_type (ctrl->audit, AUDIT_TYPE_SIGN); @@ -350,11 +565,25 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, goto leave; } + /* Note that in detached mode the b64 write is actually a binary + * writer because we need to fixup the created signature later. + * Note that we do this only for binary output because we have no + * PEM writer interface outside of the ksba create writer code. */ ctrl->pem_name = "SIGNED MESSAGE"; - rc = gnupg_ksba_create_writer - (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) - | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), - ctrl->pem_name, out_fp, &writer); + if (binary_detached) + { + sig_fp = es_fopenmem (0, "w+"); + rc = sig_fp? 0 : gpg_error_from_syserror (); + if (!rc) + rc = gnupg_ksba_create_writer (&b64writer, 0, NULL, sig_fp, &writer); + } + else + { + rc = gnupg_ksba_create_writer + (&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0) + | (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)), + ctrl->pem_name, out_fp, &writer); + } if (rc) { log_error ("can't create writer: %s\n", gpg_strerror (rc)); @@ -838,6 +1067,22 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, goto leave; } + if (binary_detached) + { + void *blob = NULL; + size_t bloblen; + + rc = es_fclose_snatch (sig_fp, &blob, &bloblen); + sig_fp = NULL; + if (rc) + goto leave; + rc = write_detached_signature (blob, bloblen, out_fp); + xfree (blob); + if (rc) + goto leave; + } + + audit_log (ctrl->audit, AUDIT_SIGNING_DONE); log_info ("signature created\n"); @@ -851,5 +1096,6 @@ gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, gnupg_ksba_destroy_writer (b64writer); keydb_release (kh); gcry_md_close (data_md); + es_fclose (sig_fp); return rc; }