aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am3
-rw-r--r--autogen.rc2
-rw-r--r--configure.ac5
-rw-r--r--tools/Makefile.am19
-rw-r--r--tools/gpgsum-w32info.rc52
-rw-r--r--tools/gpgsum.c528
-rw-r--r--tools/gpgsum.w32-manifest.in25
7 files changed, 631 insertions, 3 deletions
diff --git a/Makefile.am b/Makefile.am
index 1cd009811..d091b4850 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -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
diff --git a/autogen.rc b/autogen.rc
index a1eb96931..05d6759f5 100644
--- a/autogen.rc
+++ b/autogen.rc
@@ -10,7 +10,7 @@ case "$myhost:$myhostsub" in
extraoptions="$extraoptions --disable-zip"
;;
w32:)
- extraoptions="--enable-gpgtar"
+ extraoptions="--enable-gpgtar --enable-gpgsum"
;;
esac
diff --git a/configure.ac b/configure.ac
index 8f09bb06a..1b9af5f1a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 769a81a00..c31cba835 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -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 \
diff --git a/tools/gpgsum-w32info.rc b/tools/gpgsum-w32info.rc
new file mode 100644
index 000000000..be551ab4d
--- /dev/null
+++ b/tools/gpgsum-w32info.rc
@@ -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"
diff --git a/tools/gpgsum.c b/tools/gpgsum.c
new file mode 100644
index 000000000..967b40c19
--- /dev/null
+++ b/tools/gpgsum.c
@@ -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;
+}
diff --git a/tools/gpgsum.w32-manifest.in b/tools/gpgsum.w32-manifest.in
new file mode 100644
index 000000000..4bdedb4a9
--- /dev/null
+++ b/tools/gpgsum.w32-manifest.in
@@ -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>