tools: Add gpgsum tool

* Makefile.am: Add option for building gpgsum
* autogen.rc: Add option for building gpgsum
* configure.ac: Add option for building gpgsum
* tools/Makefile.am: Add sources for gpgsum
* tools/gpgsum-w32info.rc: Add new file
* tools/gpgsum.c: Add gpsum tool
* tools/gpgsum.w32-manifest.in: Add new file
This commit is contained in:
Tobias Fella 2023-10-11 12:07:34 +02:00
parent 98dd6f7af6
commit 3e0d1c229b
No known key found for this signature in database
GPG Key ID: F315CBBEE5E1889B
7 changed files with 631 additions and 3 deletions

View File

@ -35,7 +35,8 @@ ACLOCAL_AMFLAGS = -I m4
AM_DISTCHECK_DVI_TARGET = pdf
AM_DISTCHECK_CONFIGURE_FLAGS = --enable-gnupg-builddir-envvar \
--enable-all-tests --enable-g13 \
--enable-gpgtar --enable-wks-tools --disable-ntbtls
--enable-gpgtar --enable-wks-tools --disable-ntbtls \
--enable-gpgsum
GITLOG_TO_CHANGELOG=gitlog-to-changelog

View File

@ -10,7 +10,7 @@ case "$myhost:$myhostsub" in
extraoptions="$extraoptions --disable-zip"
;;
w32:)
extraoptions="--enable-gpgtar"
extraoptions="--enable-gpgtar --enable-gpgsum"
;;
esac

View File

@ -136,6 +136,7 @@ GNUPG_BUILD_PROGRAM(gpgtar, yes)
# We also install the gpg-wks-server tool by default but disable it
# later for platforms where it can't be build.
GNUPG_BUILD_PROGRAM(wks-tools, yes)
GNUPG_BUILD_PROGRAM(gpgsum, yes)
AC_SUBST(PACKAGE)
@ -1849,6 +1850,7 @@ AM_CONDITIONAL(BUILD_TPM2D, test "$build_tpm2d" = "yes")
AM_CONDITIONAL(BUILD_DOC, test "$build_doc" = "yes")
AM_CONDITIONAL(BUILD_GPGTAR, test "$build_gpgtar" = "yes")
AM_CONDITIONAL(BUILD_WKS_TOOLS, test "$build_wks_tools" = "yes")
AM_CONDITIONAL(BUILD_GPGSUM, test "$build_gpgsum" = "yes")
AM_CONDITIONAL(DISABLE_TESTS, test "$run_tests" != yes)
AM_CONDITIONAL(ENABLE_CARD_SUPPORT, test "$card_support" = yes)
@ -1923,6 +1925,7 @@ AC_DEFINE_UNQUOTED(GPGCONF_DISP_NAME, "GPGConf",
[The displayed name of gpgconf])
AC_DEFINE_UNQUOTED(GPGTAR_NAME, "gpgtar", [The name of the gpgtar tool])
AC_DEFINE_UNQUOTED(GPGSUM_NAME, "gpgsum", [The name of the gpgsum tool])
AC_DEFINE_UNQUOTED(GPG_AGENT_SOCK_NAME, "S.gpg-agent",
[The name of the agent socket])
@ -2118,6 +2121,7 @@ tools/gpgtar.w32-manifest
tools/gpg-check-pattern.w32-manifest
tools/gpg-wks-client.w32-manifest
tools/gpg-card.w32-manifest
tools/gpgsum.w32-manifest
])
@ -2144,6 +2148,7 @@ echo "
Keyboxd: $build_keyboxd
Gpgtar: $build_gpgtar
WKS tools: $build_wks_tools
Gpgsum: $build_gpgsum
Protect tool: $show_gnupg_protect_tool_pgm
LDAP wrapper: $show_gnupg_dirmngr_ldap_pgm

View File

@ -26,7 +26,8 @@ EXTRA_DIST = \
gpgtar-w32info.rc gpgtar.w32-manifest.in \
gpg-check-pattern-w32info.rc gpg-check-pattern.w32-manifest.in \
gpg-wks-client-w32info.rc gpg-wks-client.w32-manifest.in \
gpg-card-w32info.rc gpg-card.w32-manifest.in
gpg-card-w32info.rc gpg-card.w32-manifest.in \
gpgsum-w32info.rc gpgsum.w32-manifest.in
AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
@ -38,6 +39,7 @@ gpg_card_rc_objs = gpg-card-w32info.o
gpgtar_rc_objs = gpgtar-w32info.o
gpg_check_pattern_rc_objs = gpg-check-pattern-w32info.o
gpg_wks_client_rc_objs = gpg-wks-client-w32info.o
gpgsum_rc_objs = gpgsum-w32info.o
gpg-connect-agent-w32info.o : gpg-connect-agent.w32-manifest \
../common/w32info-rc.h
@ -48,6 +50,7 @@ gpg-check-pattern-w32info.o : gpg-check-pattern.w32-manifest \
../common/w32info-rc.h
gpg-wks-client-w32info.o : gpg-wks-client.w32-manifest \
../common/w32info-rc.h
gpgsum-w32info.o : gpgsum.w32-manifest ../common/w32info-rc.h
endif
@ -82,6 +85,12 @@ else
noinst_PROGRAMS += gpgtar
endif
if BUILD_GPGSUM
bin_PROGRAMS += gpgsum
else
noinst_PROGRAMS += gpgsum
endif
common_libs = $(libcommon)
commonpth_libs = $(libcommonpth)
@ -157,6 +166,14 @@ gpgtar_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBINTL) $(NETLIBS) $(LIBICONV) $(W32SOCKLIBS) \
$(gpgtar_rc_objs)
gpgsum_SOURCES = \
gpgsum.c
gpgsum_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS)
gpgsum_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBINTL) $(NETLIBS) \
$(gpgsum_rc_objs)
gpg_wks_server_SOURCES = \
gpg-wks-server.c \
gpg-wks.h \

52
tools/gpgsum-w32info.rc Normal file
View File

@ -0,0 +1,52 @@
/* gpgsum-w32info.rc -*- c -*-
* Copyright (C) 2020-2023 g10 Code GmbH
*
* This file is free software; as a special exception the author gives
* unlimited permission to copy and/or distribute it, with or without
* modifications, as long as this notice is preserved.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include "afxres.h"
#include "../common/w32info-rc.h"
1 ICON "../common/gnupg.ico"
1 VERSIONINFO
FILEVERSION W32INFO_VI_FILEVERSION
PRODUCTVERSION W32INFO_VI_PRODUCTVERSION
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/
#else
FILEFLAGS 0x00L
#endif
FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */
FILETYPE 0x1L /* VFT_APP (0x1) */
FILESUBTYPE 0x0L /* VFT2_UNKNOWN */
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" /* US English (0409), Unicode (04b0) */
BEGIN
VALUE "FileDescription", L"GnuPG\x2019s hashsum tool\0"
VALUE "InternalName", "gpgsum\0"
VALUE "OriginalFilename", "gpgsum.exe\0"
VALUE "ProductName", W32INFO_PRODUCTNAME
VALUE "ProductVersion", W32INFO_PRODUCTVERSION
VALUE "CompanyName", W32INFO_COMPANYNAME
VALUE "FileVersion", W32INFO_FILEVERSION
VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT
VALUE "Comments", W32INFO_COMMENTS
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 0x4b0
END
END
1 RT_MANIFEST "gpgsum.w32-manifest"

528
tools/gpgsum.c Normal file
View File

@ -0,0 +1,528 @@
/* gpgsum.c - A simple hash sum tool mainly useful for Windows.
* Copyright (C) 2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG 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 3 of the License, or
* (at your option) any later version.
*
* GnuPG 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, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <gpg-error.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
# include <fcntl.h>
# include <windows.h>
#endif
#define INCLUDED_BY_MAIN_MODULE 1
#include "../common/util.h"
#include "../common/init.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include <gcrypt.h>
static unsigned int filecount;
static unsigned int readerrors;
static unsigned int checkcount;
static unsigned int matcherrors;
struct {
int algo;
int check;
int filenames;
} opt;
enum cmd_and_opt_values
{
aNull = 0,
aCheck = 'c',
oFileNamesFromStdIn = '0',
};
static gpgrt_opt_t opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aCheck, "check",
N_("Read checksums from files and check them")),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oFileNamesFromStdIn, "filenames",
N_("Read file names from stdin")),
ARGPARSE_end ()
};
static void
parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts)
{
while (gpgrt_argparse (NULL, pargs, popts))
{
switch (pargs->r_opt)
{
case aCheck:
opt.check = 1;
break;
case oFileNamesFromStdIn:
opt.filenames = 1;
break;
default: pargs->err = 2; break;
}
}
}
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "gpgsum (@GNUPG@)";
break;
case 13: p = VERSION; break;
//case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
//case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = _("Usage: gpgsum [-c|-0] [--] FILENAMES|-");
break;
case 41:
p = _("Syntax: gpgsum [-c|-0] [--] FILENAMES|-\n"
"Create or verify hash sums for files.\n"
"The executable must be named for the desired hash algorithm "
"(i.e., \"sha2sum\").");
break;
default: p = NULL; break;
}
return p;
}
/* We need to escape the fname so that included linefeeds etc don't
mess up the the output file. On windows we also turn backslashes
into slashes so that we don't get into conflicts with the escape
character. Note that the GNU version escapes the backslash and the
LF but we also escape the CR. */
static char *
escapefname (const char *fname, int *escaped)
{
const char *s;
char *buffer;
char *d;
size_t n;
*escaped = 0;
for (n = 0, s = fname; *s; s++)
{
if (*s == '\n' || *s == '\r')
n += 2;
else if (*s == '\\')
{
#ifdef _WIN32
n++;
#else
n += 2;
#endif
}
else
n++;
}
n++;
buffer = xmalloc (n);
d = buffer;
for (s = fname; *s; s++)
{
if (*s == '\n')
{
*d++ = '\\';
*d++ = 'n' ;
*escaped = 1;
}
else if (*s == '\r')
{
*d++ = '\\';
*d++ = 'r' ;
*escaped = 1;
}
else if (*s == '\\')
{
#ifdef _WIN32
*d++ = '/';
#else
*d++ = '\\';
*d++ = '\\' ;
*escaped = 1;
#endif
}
else
*d++ = *s;
}
*d = 0;
return buffer;
}
/* Revert the escaping in-place. We handle some more of the standard
escaping characters but not all. */
static void
unescapefname (char *fname)
{
char *s, *d;
for (s=d=fname; *s; s++)
{
if (*s == '\\' && s[1])
{
s++;
switch (*s)
{
case '\\': *d++ = '\\'; break;
case 'n': *d++ = '\n'; break;
case 'r': *d++ = '\r'; break;
case 'f': *d++ = '\f'; break;
case 'v': *d++ = '\v'; break;
case 'b': *d++ = '\b'; break;
default: *d++ = '\\'; *d++ = *s; break;
}
}
else
*d++ = *s;
}
*d = 0;
}
static gpg_error_t
hash_file (const char *fname, const char *expected)
{
gpg_error_t err;
estream_t fp;
char buffer[4096];
size_t n;
char *p;
char *fnamebuf;
int escaped;
gcry_md_hd_t hd;
unsigned char *result;
unsigned int digest_length;
digest_length = gcry_md_get_algo_dlen(opt.algo);
filecount++;
if (!expected && *fname == '-' && !fname[1])
{
/* Not in check mode and asked to read from stdin. */
fp = es_stdin;
es_set_binary (es_stdin);
}
else
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("Can't open '%s': %s\n",
fname, gpg_strerror (err));
if (expected)
log_error ("%s: FAILED open\n", fname);
readerrors++;
return gpg_error(GPG_ERR_GENERAL);
}
err = gcry_md_open(&hd, opt.algo, 0);
if (err) {
log_error("Failed to open md: %s\n", gcry_strerror(err));
}
while ( (n = es_fread (buffer, 1, sizeof buffer, fp)))
gcry_md_write(hd, buffer, n);
if (es_ferror (fp))
{
log_error ("Error reading `%s': %s\n",
fname, strerror (errno));
if (fp != es_stdin)
es_fclose (fp);
if (expected)
es_printf ("%s: FAILED read\n", fname);
readerrors++;
return gpg_error(GPG_ERR_GENERAL);
}
if (fp != es_stdin)
es_fclose (fp);
fnamebuf = escapefname (fname, &escaped);
fname = fnamebuf;
result = gcry_md_read(hd, opt.algo);
if (!result) {
log_error("Failed to read digest\n");
return gpg_error(GPG_ERR_GENERAL);
}
checkcount++;
bin2hex(result, digest_length, buffer);
if (expected)
{
/* Lowercase the checksum. */
buffer[strlen(expected)] = 0;
for (p=buffer; *p; p++)
if (*p >= 'A' && *p <= 'Z')
*p |= 0x20;
if (strcmp (buffer, expected))
{
es_printf ("%s: FAILED\n", fname);
matcherrors++;
return -1;
}
es_printf ("%s: OK\n", fname);
}
else
es_printf ("%s%s %s\n", escaped? "\\":"", buffer, fname);
xfree (fnamebuf);
return 0;
}
static gpg_error_t
check_file (const char *fname)
{
estream_t fp;
char *linebuf = NULL;
char *line;
char *p;
size_t n;
int rc = 0;
int escaped;
unsigned int digest_length;
unsigned int name_offset;
size_t line_length;
size_t max_length;
digest_length = gcry_md_get_algo_dlen(opt.algo);
name_offset = digest_length * 2 + 2;
if (*fname == '-' && !fname[1])
fp = es_stdin;
else
fp = es_fopen (fname, "r");
if (!fp)
{
log_error ("Can't open '%s': %s\n", fname, strerror(errno));
return -1;
}
max_length = 4096;
while ( es_read_line (fp, &linebuf, &line_length, &max_length) )
{
escaped = (*linebuf == '\\');
line = linebuf + escaped;
n = strlen(line);
if (!n || line[n-1] != '\n')
{
log_error ("Error reading '%s': %s\n", fname,
es_feof (fp)? "last linefeed missing":"line too long");
rc = -1;
break;
}
line[--n] = 0;
if (n && line[n-1] == '\r')
line[--n] = 0;
if (!*line)
continue; /* Ignore empty lines. */
if (n < name_offset || line[name_offset-2] != ' ')
{
fprintf (stderr, "Error parsing `%s': %s\n", fname,
"invalid line");
rc = -1;
continue;
}
/* Note that we ignore the binary flag ('*') used by GNU
versions of this tool: It does not make sense to compute a
digest over some transformation of a file - we always want a
reliable checksum. The flag does not work: On Unix a
checksum file is created without the flag because it is the
default there. When checking it on Windows the missing flag
would indicate that it has been created in text mode and thus
the checksums will differ. */
/* Lowercase the checksum. */
line[name_offset-2] = 0;
for (p=line; *p; p++)
if (*p >= 'A' && *p <= 'Z')
*p |= 0x20;
/* Unescape the fname. */
if (escaped)
unescapefname (line+name_offset);
/* Hash the file. */
if (hash_file (line+name_offset, line))
rc = -1;
}
if (es_ferror (fp))
{
fprintf (stderr, "Error reading `%s': %s\n",
fname, strerror (errno));
rc = -1;
}
if (fp != stdin)
es_fclose (fp);
return rc;
}
static gpg_error_t
hash_list (void)
{
int rc = 0;
int ready = 0;
int c;
char namebuf[4096];
size_t n = 0;
unsigned long lastoff = 0;
unsigned long off = 0;
es_set_binary(es_stdin);
do
{
if ((c = es_getc (es_stdin)) == EOF)
{
if (es_ferror (es_stdin))
{
log_error ("Error reading '%s' at offset %lu: %s\n",
"[stdin]", off, strerror (errno));
rc = -1;
break;
}
/* Note: The Nul is a delimiter and not a terminator. */
c = 0;
ready = 1;
}
if (n >= sizeof namebuf)
{
log_error ("Error reading '%s': "
"filename at offset %lu too long\n",
"[stdin]", lastoff);
rc = -1;
break;
}
namebuf[n++] = c;
off++;
if (!c)
{
if (*namebuf && hash_file (namebuf, NULL))
rc = -1;
n = 0;
lastoff = off;
}
}
while (!ready);
return rc;
}
int
main (int argc, char **argv)
{
gpgrt_argparse_t pargs;
char *executable = argv[0];
char *maybe_executable;
char *algo_str;
int rc = 0;
gnupg_reopen_std ("gpgsum");
i18n_init();
init_common_subsystems(&argc, &argv);
gpgrt_set_strusage (my_strusage);
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = ARGPARSE_FLAG_KEEP;
parse_arguments (&pargs, opts);
gpgrt_argparse (NULL, &pargs, NULL);
#ifdef _WIN32
maybe_executable = strrchr(executable, '\\');
#else
maybe_executable = strrchr(executable, '/');
#endif
if (maybe_executable)
{
executable = ++maybe_executable;
}
algo_str = executable;
if (strlen(executable) > 3)
{
algo_str = gpgrt_strdup(executable);
if (strstr(algo_str, ".exe") != NULL)
algo_str[strlen(algo_str) - 7] = 0;
else
algo_str[strlen(algo_str) - 3] = 0;
}
opt.algo = gcry_md_map_name(algo_str);
if (!opt.algo)
{
//unknown algo
gpgrt_usage (1);
}
gpgrt_free(algo_str);
if (opt.filenames && opt.check) {
gpgrt_usage (1);
}
if (opt.filenames)
{
/* With option -0 a dash must be given as filename. */
if (argc != 1 || strcmp (argv[0], "-"))
gpgrt_usage (1);
if (hash_list ())
rc = 1;
}
else
{
for (; argc; argv++, argc--)
{
if (opt.check)
{
if (check_file (*argv))
rc = 1;
}
else
{
if (hash_file (*argv, NULL))
rc = 1;
}
}
}
if (opt.check && readerrors)
log_error ("WARNING: %u of %u listed files could not be read\n",
readerrors, filecount);
if (opt.check && matcherrors)
log_error ("WARNING: %u of %u computed checksums did NOT match\n",
matcherrors, checkcount);
return rc;
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<description>GNU Privacy Guard (Hashsum tool)</description>
<assemblyIdentity
type="win32"
name="GnuPG.gpgsum"
version="@BUILD_VERSION@"
/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- 10 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><!-- 8.1 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><!-- 8 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><!-- 7 -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><!-- Vista -->
</application>
</compatibility>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>