/* $Id: RTSignTool.cpp $ */ /** @file * IPRT - Signing Tool. */ /* * Copyright (C) 2006-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program 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, in version 3 of the * License. * * This program 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 . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef RT_OS_WINDOWS # include #endif #include #include #include #include #ifndef RT_OS_WINDOWS # include #else # define WIN_CERTIFICATE_ALIGNMENT UINT32_C(8) /* from pecoff.h */ #endif #include #include #include #include #include #include #include #include #include #ifdef VBOX # include /* Certificates */ #endif #ifdef RT_OS_WINDOWS # include # include # include # include #endif #include "internal/ldr.h" /* for IMAGE_XX_SIGNATURE defines */ /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ #define OPT_OFF_CERT_FILE 0 /**< signtool /f file */ #define OPT_OFF_CERT_SHA1 1 /**< signtool /sha1 thumbprint */ #define OPT_OFF_CERT_SUBJECT 2 /**< signtool /n name */ #define OPT_OFF_CERT_STORE 3 /**< signtool /s store */ #define OPT_OFF_CERT_STORE_MACHINE 4 /**< signtool /sm */ #define OPT_OFF_KEY_FILE 5 /**< no signtool equivalent, other than maybe /f. */ #define OPT_OFF_KEY_PASSWORD 6 /**< signtool /p pass */ #define OPT_OFF_KEY_PASSWORD_FILE 7 /**< no signtool equivalent. */ #define OPT_OFF_KEY_NAME 8 /**< signtool /kc name */ #define OPT_OFF_KEY_PROVIDER 9 /**< signtool /csp name (CSP = cryptographic service provider) */ #define OPT_CERT_KEY_SWITCH_CASES(a_Instance, a_uBase, a_chOpt, a_ValueUnion, a_rcExit) \ case (a_uBase) + OPT_OFF_CERT_FILE: \ case (a_uBase) + OPT_OFF_CERT_SHA1: \ case (a_uBase) + OPT_OFF_CERT_SUBJECT: \ case (a_uBase) + OPT_OFF_CERT_STORE: \ case (a_uBase) + OPT_OFF_CERT_STORE_MACHINE: \ case (a_uBase) + OPT_OFF_KEY_FILE: \ case (a_uBase) + OPT_OFF_KEY_PASSWORD: \ case (a_uBase) + OPT_OFF_KEY_PASSWORD_FILE: \ case (a_uBase) + OPT_OFF_KEY_NAME: \ case (a_uBase) + OPT_OFF_KEY_PROVIDER: \ a_rcExit = a_Instance.handleOption((a_chOpt) - (a_uBase), &(a_ValueUnion)); \ break #define OPT_CERT_KEY_GETOPTDEF_ENTRIES(a_szPrefix, a_szSuffix, a_uBase) \ { a_szPrefix "cert-file" a_szSuffix, (a_uBase) + OPT_OFF_CERT_FILE, RTGETOPT_REQ_STRING }, \ { a_szPrefix "cert-sha1" a_szSuffix, (a_uBase) + OPT_OFF_CERT_SHA1, RTGETOPT_REQ_STRING }, \ { a_szPrefix "cert-subject" a_szSuffix, (a_uBase) + OPT_OFF_CERT_SUBJECT, RTGETOPT_REQ_STRING }, \ { a_szPrefix "cert-store" a_szSuffix, (a_uBase) + OPT_OFF_CERT_STORE, RTGETOPT_REQ_STRING }, \ { a_szPrefix "cert-machine-store" a_szSuffix, (a_uBase) + OPT_OFF_CERT_STORE_MACHINE, RTGETOPT_REQ_NOTHING }, \ { a_szPrefix "key-file" a_szSuffix, (a_uBase) + OPT_OFF_KEY_FILE, RTGETOPT_REQ_STRING }, \ { a_szPrefix "key-password" a_szSuffix, (a_uBase) + OPT_OFF_KEY_PASSWORD, RTGETOPT_REQ_STRING }, \ { a_szPrefix "key-password-file" a_szSuffix, (a_uBase) + OPT_OFF_KEY_PASSWORD_FILE, RTGETOPT_REQ_STRING }, \ { a_szPrefix "key-name" a_szSuffix, (a_uBase) + OPT_OFF_KEY_NAME, RTGETOPT_REQ_STRING }, \ { a_szPrefix "key-provider" a_szSuffix, (a_uBase) + OPT_OFF_KEY_PROVIDER, RTGETOPT_REQ_STRING } #define OPT_CERT_KEY_GETOPTDEF_COMPAT_ENTRIES(a_uBase) \ { "/f", (a_uBase) + OPT_OFF_CERT_FILE, RTGETOPT_REQ_STRING }, \ { "/sha1", (a_uBase) + OPT_OFF_CERT_SHA1, RTGETOPT_REQ_STRING }, \ { "/n", (a_uBase) + OPT_OFF_CERT_SUBJECT, RTGETOPT_REQ_STRING }, \ { "/s", (a_uBase) + OPT_OFF_CERT_STORE, RTGETOPT_REQ_STRING }, \ { "/sm", (a_uBase) + OPT_OFF_CERT_STORE_MACHINE, RTGETOPT_REQ_NOTHING }, \ { "/p", (a_uBase) + OPT_OFF_KEY_PASSWORD, RTGETOPT_REQ_STRING }, \ { "/kc", (a_uBase) + OPT_OFF_KEY_NAME, RTGETOPT_REQ_STRING }, \ { "/csp", (a_uBase) + OPT_OFF_KEY_PROVIDER, RTGETOPT_REQ_STRING } #define OPT_CERT_KEY_SYNOPSIS(a_szPrefix, a_szSuffix) \ "[" a_szPrefix "cert-file" a_szSuffix " ] " \ "[" a_szPrefix "cert-sha1" a_szSuffix " ] " \ "[" a_szPrefix "cert-subject" a_szSuffix " ] " \ "[" a_szPrefix "cert-store" a_szSuffix " ] " \ "[" a_szPrefix "cert-machine-store" a_szSuffix "] " \ "[" a_szPrefix "key-file" a_szSuffix " ] " \ "[" a_szPrefix "key-password" a_szSuffix " ] " \ "[" a_szPrefix "key-password-file" a_szSuffix " |stdin] " \ "[" a_szPrefix "key-name" a_szSuffix " ] " \ "[" a_szPrefix "key-provider" a_szSuffix " ] " #define OPT_HASH_PAGES 1200 #define OPT_NO_HASH_PAGES 1201 #define OPT_ADD_CERT 1202 #define OPT_TIMESTAMP_TYPE 1203 #define OPT_TIMESTAMP_TYPE_2 1204 #define OPT_TIMESTAMP_OVERRIDE 1205 #define OPT_NO_SIGNING_TIME 1206 #define OPT_FILE_TYPE 1207 #define OPT_IGNORED 1208 /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** Help detail levels. */ typedef enum RTSIGNTOOLHELP { RTSIGNTOOLHELP_USAGE, RTSIGNTOOLHELP_FULL } RTSIGNTOOLHELP; /** Filetypes. */ typedef enum RTSIGNTOOLFILETYPE { RTSIGNTOOLFILETYPE_INVALID = 0, RTSIGNTOOLFILETYPE_DETECT, RTSIGNTOOLFILETYPE_EXE, RTSIGNTOOLFILETYPE_CAT, RTSIGNTOOLFILETYPE_UNKNOWN, RTSIGNTOOLFILETYPE_END } RTSIGNTOOLFILETYPE; /** * PKCS\#7 signature data. */ typedef struct SIGNTOOLPKCS7 { /** The file type. */ RTSIGNTOOLFILETYPE enmType; /** The raw signature. */ uint8_t *pbBuf; /** Size of the raw signature. */ size_t cbBuf; /** The filename. */ const char *pszFilename; /** The outer content info wrapper. */ RTCRPKCS7CONTENTINFO ContentInfo; /** Pointer to the decoded SignedData inside the ContentInfo member. */ PRTCRPKCS7SIGNEDDATA pSignedData; /** Newly encoded raw signature. * @sa SignToolPkcs7_Encode() */ uint8_t *pbNewBuf; /** Size of newly encoded raw signature. */ size_t cbNewBuf; } SIGNTOOLPKCS7; typedef SIGNTOOLPKCS7 *PSIGNTOOLPKCS7; /** * PKCS\#7 signature data for executable. */ typedef struct SIGNTOOLPKCS7EXE : public SIGNTOOLPKCS7 { /** The module handle. */ RTLDRMOD hLdrMod; } SIGNTOOLPKCS7EXE; typedef SIGNTOOLPKCS7EXE *PSIGNTOOLPKCS7EXE; /** * Data for the show exe (signature) command. */ typedef struct SHOWEXEPKCS7 : public SIGNTOOLPKCS7EXE { /** The verbosity. */ unsigned cVerbosity; /** The prefix buffer. */ char szPrefix[256]; /** Temporary buffer. */ char szTmp[4096]; } SHOWEXEPKCS7; typedef SHOWEXEPKCS7 *PSHOWEXEPKCS7; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ static RTEXITCODE HandleHelp(int cArgs, char **papszArgs); static RTEXITCODE HelpHelp(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel); static RTEXITCODE HandleVersion(int cArgs, char **papszArgs); static int HandleShowExeWorkerPkcs7DisplaySignerInfo(PSHOWEXEPKCS7 pThis, size_t offPrefix, PCRTCRPKCS7SIGNERINFO pSignerInfo); static int HandleShowExeWorkerPkcs7Display(PSHOWEXEPKCS7 pThis, PRTCRPKCS7SIGNEDDATA pSignedData, size_t offPrefix, PCRTCRPKCS7CONTENTINFO pContentInfo); /********************************************************************************************************************************* * Certificate and Private Key Handling (options, ++). * *********************************************************************************************************************************/ #ifdef RT_OS_WINDOWS /** @todo create a better fake certificate. */ const unsigned char g_abFakeCertificate[] = { 0x30, 0x82, 0x03, 0xb2, 0x30, 0x82, 0x02, 0x9a, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x31, /* 0x00000000: 0...0..........1 */ 0xba, 0xd6, 0xbc, 0x5d, 0x9a, 0xe0, 0xb0, 0x4e, 0xd4, 0xfa, 0xcc, 0xfb, 0x47, 0x00, 0x5c, 0x30, /* 0x00000010: ...]...N....G.\0 */ 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x71, /* 0x00000020: ...*.H........0q */ 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x13, 0x54, 0x69, 0x6d, 0x65, 0x73, /* 0x00000030: 1.0...U....Times */ 0x74, 0x61, 0x6d, 0x70, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x32, 0x31, 0x0c, /* 0x00000040: tamp Signing 21. */ 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x03, 0x44, 0x65, 0x76, 0x31, 0x15, 0x30, 0x13, /* 0x00000050: 0...U....Dev1.0. */ 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0c, 0x54, 0x65, 0x73, 0x74, 0x20, 0x43, 0x6f, 0x6d, 0x70, /* 0x00000060: ..U....Test Comp */ 0x61, 0x6e, 0x79, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x09, 0x53, 0x74, /* 0x00000070: any1.0...U....St */ 0x75, 0x74, 0x74, 0x67, 0x61, 0x72, 0x74, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, /* 0x00000080: uttgart1.0...U.. */ 0x0c, 0x02, 0x42, 0x42, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x44, /* 0x00000090: ..BB1.0...U....D */ 0x45, 0x30, 0x1e, 0x17, 0x0d, 0x30, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x31, 0x30, /* 0x000000a0: E0...00010100010 */ 0x31, 0x5a, 0x17, 0x0d, 0x33, 0x36, 0x31, 0x32, 0x33, 0x31, 0x32, 0x32, 0x35, 0x39, 0x35, 0x39, /* 0x000000b0: 1Z..361231225959 */ 0x5a, 0x30, 0x71, 0x31, 0x1c, 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x13, 0x54, 0x69, /* 0x000000c0: Z0q1.0...U....Ti */ 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x20, /* 0x000000d0: mestamp Signing */ 0x32, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x03, 0x44, 0x65, 0x76, 0x31, /* 0x000000e0: 21.0...U....Dev1 */ 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x0c, 0x54, 0x65, 0x73, 0x74, 0x20, 0x43, /* 0x000000f0: .0...U....Test C */ 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, /* 0x00000100: ompany1.0...U... */ 0x09, 0x53, 0x74, 0x75, 0x74, 0x74, 0x67, 0x61, 0x72, 0x74, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, /* 0x00000110: .Stuttgart1.0... */ 0x55, 0x04, 0x08, 0x0c, 0x02, 0x42, 0x42, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, /* 0x00000120: U....BB1.0...U.. */ 0x13, 0x02, 0x44, 0x45, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, /* 0x00000130: ..DE0.."0...*.H. */ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, /* 0x00000140: ............0... */ 0x02, 0x82, 0x01, 0x01, 0x00, 0xdb, 0x18, 0x63, 0x33, 0xf2, 0x08, 0x90, 0x5a, 0xab, 0xda, 0x88, /* 0x00000150: .......c3...Z... */ 0x73, 0x86, 0x49, 0xea, 0x8b, 0xaf, 0xcf, 0x67, 0x15, 0xa5, 0x39, 0xe6, 0xa2, 0x94, 0x0c, 0x3f, /* 0x00000160: s.I....g..9....? */ 0xa1, 0x2e, 0x6c, 0xd2, 0xdf, 0x01, 0x65, 0x6d, 0xed, 0x6c, 0x4c, 0xac, 0xe7, 0x77, 0x7a, 0x45, /* 0x00000170: ..l...em.lL..wzE */ 0x05, 0x6b, 0x24, 0xf3, 0xaf, 0x45, 0x35, 0x6e, 0x64, 0x0a, 0xac, 0x1d, 0x37, 0xe1, 0x33, 0xa4, /* 0x00000180: .k$..E5nd...7.3. */ 0x92, 0xec, 0x45, 0xe8, 0x99, 0xc1, 0xde, 0x6f, 0xab, 0x7c, 0xf0, 0xdc, 0xe2, 0xc5, 0x42, 0xa3, /* 0x00000190: ..E....o.|....B. */ 0xea, 0xf5, 0x8a, 0xf9, 0x0e, 0xe7, 0xb3, 0x35, 0xa2, 0x75, 0x5e, 0x87, 0xd2, 0x2a, 0xd1, 0x27, /* 0x000001a0: .......5.u^..*.' */ 0xa6, 0x79, 0x9e, 0xfe, 0x90, 0xbf, 0x97, 0xa4, 0xa1, 0xd8, 0xf7, 0xd7, 0x05, 0x59, 0x44, 0x27, /* 0x000001b0: .y...........YD' */ 0x39, 0x6e, 0x33, 0x01, 0x2e, 0x46, 0x92, 0x47, 0xbe, 0x50, 0x91, 0x26, 0x27, 0xe5, 0x4b, 0x3a, /* 0x000001c0: 9n3..F.G.P.&'.K: */ 0x76, 0x26, 0x64, 0x92, 0x0c, 0xa0, 0x54, 0x43, 0x6f, 0x56, 0xcc, 0x7b, 0xd0, 0xe3, 0xd8, 0x39, /* 0x000001d0: v&d...TCoV.{...9 */ 0x5f, 0xb9, 0x41, 0xda, 0x1c, 0x62, 0x88, 0x0c, 0x45, 0x03, 0x63, 0xf8, 0xff, 0xe5, 0x3e, 0x87, /* 0x000001e0: _.A..b..E.c...>. */ 0x0c, 0x75, 0xc9, 0xdd, 0xa2, 0xc0, 0x1b, 0x63, 0x19, 0xeb, 0x09, 0x9d, 0xa1, 0xbb, 0x0f, 0x63, /* 0x000001f0: .u.....c.......c */ 0x67, 0x1c, 0xa3, 0xfd, 0x2f, 0xd1, 0x2a, 0xda, 0xd8, 0x93, 0x66, 0x45, 0x54, 0xef, 0x8b, 0x6d, /* 0x00000200: g.....*...fET..m */ 0x12, 0x15, 0x0f, 0xd4, 0xb5, 0x04, 0x17, 0x30, 0x5b, 0xfa, 0x12, 0x96, 0x48, 0x5b, 0x38, 0x65, /* 0x00000210: .......0[...H[8e */ 0xfd, 0x8f, 0x0c, 0xa3, 0x11, 0x46, 0x49, 0xe0, 0x62, 0xc3, 0xcc, 0x34, 0xe6, 0xfb, 0xab, 0x51, /* 0x00000220: .....FI.b..4...Q */ 0xc3, 0xd4, 0x0b, 0xdc, 0x39, 0x93, 0x87, 0x90, 0x10, 0x9f, 0xce, 0x43, 0x27, 0x31, 0xd5, 0x4e, /* 0x00000230: ....9......C'1.N */ 0x52, 0x60, 0xf1, 0x93, 0xd5, 0x06, 0xc4, 0x4e, 0x65, 0xb6, 0x35, 0x4a, 0x64, 0x15, 0xf8, 0xaf, /* 0x00000240: R`.....Ne.5Jd... */ 0x71, 0xb2, 0x42, 0x50, 0x89, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x46, 0x30, 0x44, 0x30, 0x0e, /* 0x00000250: q.BP.......F0D0. */ 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x07, 0x80, 0x30, 0x13, /* 0x00000260: ..U...........0. */ 0x06, 0x03, 0x55, 0x1d, 0x25, 0x04, 0x0c, 0x30, 0x0a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, /* 0x00000270: ..U.%..0...+.... */ 0x07, 0x03, 0x08, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x52, 0x9d, /* 0x00000280: ...0...U......R. */ 0x4d, 0xcd, 0x41, 0xe1, 0xd2, 0x68, 0x22, 0xd3, 0x10, 0x33, 0x01, 0xca, 0xff, 0x00, 0x1d, 0x27, /* 0x00000290: M.A..h"..3.....' */ 0xa4, 0x01, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, /* 0x000002a0: ..0...*.H....... */ 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0xc5, 0x5a, 0x51, 0x83, 0x68, 0x3f, 0x06, 0x39, 0x79, 0x13, /* 0x000002b0: .......ZQ.h?.9y. */ 0xa6, 0xf0, 0x1a, 0xf9, 0x29, 0x16, 0x2d, 0xa2, 0x07, 0xaa, 0x9b, 0xc3, 0x13, 0x88, 0x39, 0x69, /* 0x000002c0: ....).-.......9i */ 0xba, 0xf7, 0x0d, 0xfb, 0xc0, 0x6e, 0x3a, 0x0b, 0x49, 0x10, 0xd1, 0xbe, 0x36, 0x91, 0x3f, 0x9d, /* 0x000002d0: .....n:.I...6.?. */ 0xa1, 0xe8, 0xc4, 0x91, 0xf9, 0x02, 0xe1, 0xf1, 0x01, 0x15, 0x09, 0xb7, 0xa1, 0xf1, 0xec, 0x43, /* 0x000002e0: ...............C */ 0x0d, 0x73, 0xd1, 0x31, 0x02, 0x4a, 0xce, 0x21, 0xf2, 0xa7, 0x99, 0x7c, 0xee, 0x85, 0x54, 0xc0, /* 0x000002f0: .s.1.J.!...|..T. */ 0x55, 0x9b, 0x19, 0x37, 0xe8, 0xcf, 0x94, 0x41, 0x10, 0x6e, 0x67, 0xdd, 0x86, 0xaf, 0xb7, 0xfe, /* 0x00000300: U..7...A.ng..... */ 0x50, 0x05, 0xf6, 0xfb, 0x0a, 0xdf, 0x88, 0xb5, 0x59, 0x69, 0x98, 0x27, 0xf8, 0x81, 0x6a, 0x4a, /* 0x00000310: P.......Yi.'..jJ */ 0x7c, 0xf3, 0x63, 0xa9, 0x41, 0x78, 0x76, 0x12, 0xdb, 0x0e, 0x94, 0x0a, 0xdb, 0x1d, 0x3c, 0x87, /* 0x00000320: |.c.Axv.......<. */ 0x35, 0xca, 0x28, 0xeb, 0xb0, 0x62, 0x27, 0x69, 0xe2, 0xf3, 0x84, 0x48, 0xa2, 0x2d, 0xd7, 0x0e, /* 0x00000330: 5.(..b'i...H.-.. */ 0x4b, 0x6d, 0x39, 0xa7, 0x3e, 0x04, 0x94, 0x8e, 0xb6, 0x4b, 0x91, 0x01, 0x68, 0xf9, 0xd2, 0x75, /* 0x00000340: Km9.>....K..h..u */ 0x1b, 0xac, 0x42, 0x3b, 0x85, 0xfc, 0x5b, 0x48, 0x3a, 0x13, 0xe7, 0x1c, 0x17, 0xcd, 0x84, 0x89, /* 0x00000350: ..B;..[H:....... */ 0x9e, 0x5f, 0xe3, 0x77, 0xc0, 0xae, 0x34, 0xc3, 0x87, 0x76, 0x4a, 0x23, 0x30, 0xa0, 0xe1, 0x45, /* 0x00000360: ._.w..4..vJ#0..E */ 0x94, 0x2a, 0x5b, 0x6b, 0x5a, 0xf0, 0x1a, 0x7e, 0xa6, 0xc4, 0xed, 0xe4, 0xac, 0x5d, 0xdf, 0x87, /* 0x00000370: .*[kZ..~.....].. */ 0x8f, 0xc5, 0xb4, 0x8c, 0xbc, 0x70, 0xc1, 0xf7, 0xb2, 0x72, 0xbd, 0x73, 0xc9, 0x4e, 0xed, 0x8d, /* 0x00000380: .....p...r.s.N.. */ 0x29, 0x33, 0xe9, 0x14, 0xc1, 0x5e, 0xff, 0x39, 0xa8, 0xe7, 0x9a, 0x3b, 0x7a, 0x3c, 0xce, 0x5d, /* 0x00000390: )3...^.9...;z<.] */ 0x0f, 0x3c, 0x82, 0x90, 0xff, 0x81, 0x82, 0x00, 0x82, 0x5f, 0xba, 0x08, 0x79, 0xb1, 0x97, 0xc3, /* 0x000003a0: .<......._..y... */ 0x09, 0x75, 0xc0, 0x04, 0x9b, 0x67, /* 0x000003b0: .u...g */ }; const unsigned char g_abFakeRsaKey[] = { 0x30, 0x82, 0x04, 0xa4, 0x02, 0x01, 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xdb, 0x18, 0x63, 0x33, /* 0x00000000: 0.............c3 */ 0xf2, 0x08, 0x90, 0x5a, 0xab, 0xda, 0x88, 0x73, 0x86, 0x49, 0xea, 0x8b, 0xaf, 0xcf, 0x67, 0x15, /* 0x00000010: ...Z...s.I....g. */ 0xa5, 0x39, 0xe6, 0xa2, 0x94, 0x0c, 0x3f, 0xa1, 0x2e, 0x6c, 0xd2, 0xdf, 0x01, 0x65, 0x6d, 0xed, /* 0x00000020: .9....?..l...em. */ 0x6c, 0x4c, 0xac, 0xe7, 0x77, 0x7a, 0x45, 0x05, 0x6b, 0x24, 0xf3, 0xaf, 0x45, 0x35, 0x6e, 0x64, /* 0x00000030: lL..wzE.k$..E5nd */ 0x0a, 0xac, 0x1d, 0x37, 0xe1, 0x33, 0xa4, 0x92, 0xec, 0x45, 0xe8, 0x99, 0xc1, 0xde, 0x6f, 0xab, /* 0x00000040: ...7.3...E....o. */ 0x7c, 0xf0, 0xdc, 0xe2, 0xc5, 0x42, 0xa3, 0xea, 0xf5, 0x8a, 0xf9, 0x0e, 0xe7, 0xb3, 0x35, 0xa2, /* 0x00000050: |....B........5. */ 0x75, 0x5e, 0x87, 0xd2, 0x2a, 0xd1, 0x27, 0xa6, 0x79, 0x9e, 0xfe, 0x90, 0xbf, 0x97, 0xa4, 0xa1, /* 0x00000060: u^..*.'.y....... */ 0xd8, 0xf7, 0xd7, 0x05, 0x59, 0x44, 0x27, 0x39, 0x6e, 0x33, 0x01, 0x2e, 0x46, 0x92, 0x47, 0xbe, /* 0x00000070: ....YD'9n3..F.G. */ 0x50, 0x91, 0x26, 0x27, 0xe5, 0x4b, 0x3a, 0x76, 0x26, 0x64, 0x92, 0x0c, 0xa0, 0x54, 0x43, 0x6f, /* 0x00000080: P.&'.K:v&d...TCo */ 0x56, 0xcc, 0x7b, 0xd0, 0xe3, 0xd8, 0x39, 0x5f, 0xb9, 0x41, 0xda, 0x1c, 0x62, 0x88, 0x0c, 0x45, /* 0x00000090: V.{...9_.A..b..E */ 0x03, 0x63, 0xf8, 0xff, 0xe5, 0x3e, 0x87, 0x0c, 0x75, 0xc9, 0xdd, 0xa2, 0xc0, 0x1b, 0x63, 0x19, /* 0x000000a0: .c...>..u.....c. */ 0xeb, 0x09, 0x9d, 0xa1, 0xbb, 0x0f, 0x63, 0x67, 0x1c, 0xa3, 0xfd, 0x2f, 0xd1, 0x2a, 0xda, 0xd8, /* 0x000000b0: ......cg.....*.. */ 0x93, 0x66, 0x45, 0x54, 0xef, 0x8b, 0x6d, 0x12, 0x15, 0x0f, 0xd4, 0xb5, 0x04, 0x17, 0x30, 0x5b, /* 0x000000c0: .fET..m.......0[ */ 0xfa, 0x12, 0x96, 0x48, 0x5b, 0x38, 0x65, 0xfd, 0x8f, 0x0c, 0xa3, 0x11, 0x46, 0x49, 0xe0, 0x62, /* 0x000000d0: ...H[8e.....FI.b */ 0xc3, 0xcc, 0x34, 0xe6, 0xfb, 0xab, 0x51, 0xc3, 0xd4, 0x0b, 0xdc, 0x39, 0x93, 0x87, 0x90, 0x10, /* 0x000000e0: ..4...Q....9.... */ 0x9f, 0xce, 0x43, 0x27, 0x31, 0xd5, 0x4e, 0x52, 0x60, 0xf1, 0x93, 0xd5, 0x06, 0xc4, 0x4e, 0x65, /* 0x000000f0: ..C'1.NR`.....Ne */ 0xb6, 0x35, 0x4a, 0x64, 0x15, 0xf8, 0xaf, 0x71, 0xb2, 0x42, 0x50, 0x89, 0x02, 0x03, 0x01, 0x00, /* 0x00000100: .5Jd...q.BP..... */ 0x01, 0x02, 0x82, 0x01, 0x01, 0x00, 0xd0, 0x5e, 0x09, 0x3a, 0xc5, 0xdc, 0xcf, 0x2c, 0xec, 0x74, /* 0x00000110: .......^.:...,.t */ 0x11, 0x81, 0x8d, 0x1d, 0x8f, 0x2a, 0xfa, 0x31, 0x4d, 0xe0, 0x90, 0x1a, 0xd8, 0xf5, 0x95, 0xc7, /* 0x00000120: .....*.1M....... */ 0x70, 0x5c, 0x62, 0x42, 0xac, 0xe9, 0xd9, 0xf2, 0x14, 0xf1, 0xd0, 0x25, 0xbb, 0xeb, 0x06, 0xfe, /* 0x00000130: p\bB.......%.... */ 0x09, 0xd6, 0x75, 0x67, 0xd7, 0x39, 0xc1, 0xa0, 0x67, 0x34, 0x4d, 0xd2, 0x12, 0x97, 0xaa, 0x5d, /* 0x00000140: ..ug.9..g4M....] */ 0xeb, 0x0e, 0xb0, 0x16, 0x6c, 0x78, 0x8e, 0xa0, 0x75, 0xa3, 0xaa, 0x57, 0x88, 0x3b, 0x43, 0x4f, /* 0x00000150: ....lx..u..W.;CO */ 0x75, 0x85, 0x67, 0xb0, 0x9b, 0xdd, 0x49, 0x0e, 0x6e, 0xdb, 0xea, 0xb3, 0xd4, 0x88, 0x54, 0xa0, /* 0x00000160: u.g...I.n.....T. */ 0x46, 0x0d, 0x55, 0x6d, 0x98, 0xbd, 0x20, 0xf9, 0x9f, 0x61, 0x2d, 0x6f, 0xc7, 0xd7, 0x16, 0x66, /* 0x00000170: F.Um.. ..a-o...f */ 0x72, 0xc7, 0x73, 0xbe, 0x9e, 0x48, 0xdc, 0x65, 0x12, 0x46, 0x35, 0x69, 0x55, 0xd8, 0x6b, 0x81, /* 0x00000180: r.s..H.e.F5iU.k. */ 0x78, 0x40, 0x15, 0x93, 0x60, 0x31, 0x4e, 0x87, 0x15, 0x2a, 0x74, 0x74, 0x7b, 0xa0, 0x1f, 0x59, /* 0x00000190: x@..`1N..*tt{..Y */ 0x8d, 0xc8, 0x3f, 0xdd, 0xf0, 0x13, 0x88, 0x2a, 0x4a, 0xf2, 0xf5, 0xf1, 0x9e, 0xf3, 0x2d, 0x9c, /* 0x000001a0: ..?....*J.....-. */ 0x8e, 0xbc, 0xb1, 0x21, 0x45, 0xc7, 0x44, 0x0c, 0x6a, 0xfe, 0x4c, 0x20, 0xdc, 0x73, 0xda, 0x62, /* 0x000001b0: ...!E.D.j.L .s.b */ 0x21, 0xcb, 0xdf, 0x06, 0xfc, 0x90, 0xc2, 0xbd, 0xd6, 0xde, 0xfb, 0xf6, 0x08, 0x69, 0x5d, 0xea, /* 0x000001c0: !............i]. */ 0xb3, 0x7f, 0x93, 0x61, 0xf2, 0xc1, 0xd0, 0x61, 0x4f, 0xd5, 0x5b, 0x63, 0xba, 0xb0, 0x3b, 0x07, /* 0x000001d0: ...a...aO.[c..;. */ 0x7a, 0x55, 0xcd, 0xa1, 0xae, 0x8a, 0x92, 0x21, 0xcc, 0x2f, 0x5b, 0xf8, 0x40, 0x6a, 0xcd, 0xd5, /* 0x000001e0: zU.....!..[.@j.. */ 0x5f, 0x15, 0xf4, 0xb6, 0xbd, 0xe5, 0x91, 0xb9, 0xa8, 0xcc, 0x2a, 0xa8, 0xa6, 0x67, 0x57, 0x2b, /* 0x000001f0: _.........*..gW+ */ 0x4b, 0xe9, 0x88, 0xe0, 0xbb, 0x58, 0xac, 0x69, 0x5f, 0x3c, 0x76, 0x28, 0xa6, 0x9d, 0xbc, 0x71, /* 0x00000200: K....X.i_......; */ 0xd6, 0x4b, 0x38, 0x69, 0x9b, 0x71, 0x29, 0x89, 0xd4, 0x6d, 0x8c, 0x41, 0xee, 0xe2, 0x4d, 0xfc, /* 0x000002a0: .K8i.q)..m.A..M. */ 0xf0, 0x9a, 0x73, 0xf1, 0x15, 0x94, 0xac, 0x1b, 0x68, 0x5f, 0x79, 0x15, 0x3a, 0x41, 0x55, 0x09, /* 0x000002b0: ..s.....h_y.:AU. */ 0xc7, 0x1e, 0xec, 0x27, 0x67, 0xe2, 0xdc, 0x54, 0xa8, 0x09, 0xe6, 0x46, 0x92, 0x92, 0x03, 0x8d, /* 0x000002c0: ...'g..T...F.... */ 0xe5, 0x96, 0xfb, 0x1a, 0xdd, 0x59, 0x6f, 0x92, 0xf1, 0xf6, 0x8f, 0x76, 0xb0, 0xc5, 0xe6, 0xd7, /* 0x000002d0: .....Yo....v.... */ 0x1b, 0x25, 0xaf, 0x04, 0x9f, 0xd8, 0x71, 0x27, 0x97, 0x99, 0x23, 0x09, 0x7d, 0xef, 0x06, 0x13, /* 0x000002e0: .%....q'..#.}... */ 0xab, 0xdc, 0xa2, 0xd8, 0x5f, 0xc5, 0xec, 0xf3, 0x62, 0x20, 0x72, 0x7b, 0xa8, 0xc7, 0x09, 0x24, /* 0x000002f0: ...._...b r{...$ */ 0xaf, 0x72, 0xc9, 0xea, 0xb8, 0x2d, 0xda, 0x00, 0xc8, 0xfe, 0xb4, 0x9f, 0x9f, 0xc7, 0xa9, 0xf7, /* 0x00000300: .r...-.......... */ 0x1d, 0xce, 0xb1, 0xdb, 0xc5, 0x8a, 0x4e, 0xe8, 0x88, 0x77, 0x68, 0xdd, 0xf8, 0x77, 0x02, 0x81, /* 0x00000310: ......N..wh..w.. */ 0x80, 0x5b, 0xa5, 0x8e, 0x98, 0x01, 0xa8, 0xd3, 0x37, 0x33, 0x37, 0x11, 0x7e, 0xbe, 0x02, 0x07, /* 0x00000320: .[......737.~... */ 0xf4, 0x56, 0x3f, 0xe9, 0x9f, 0xf1, 0x20, 0xc3, 0xf0, 0x4f, 0xdc, 0xf9, 0xfe, 0x40, 0xd3, 0x30, /* 0x00000330: .V?... ..O...@.0 */ 0xc7, 0xe3, 0x2a, 0x92, 0xec, 0x56, 0xf8, 0x17, 0xa5, 0x7b, 0x4a, 0x37, 0x11, 0xcd, 0x27, 0x26, /* 0x00000340: ..*..V...{J7..'& */ 0x8a, 0xba, 0x43, 0xda, 0x96, 0xc6, 0x0b, 0x6c, 0xe8, 0x78, 0x30, 0xea, 0x30, 0x4e, 0x7a, 0xd3, /* 0x00000350: ..C....l.x0.0Nz. */ 0xd8, 0xd2, 0xd8, 0xca, 0x3d, 0xe2, 0xad, 0xa2, 0x74, 0x73, 0x1e, 0xbe, 0xb7, 0xad, 0x41, 0x61, /* 0x00000360: ....=...ts....Aa */ 0x9b, 0xaa, 0xc9, 0xf9, 0xa4, 0xf1, 0x79, 0x4f, 0x42, 0x10, 0xc7, 0x36, 0x03, 0x4b, 0x0d, 0xdc, /* 0x00000370: ......yOB..6.K.. */ 0xef, 0x3a, 0xa3, 0xab, 0x09, 0xe4, 0xe8, 0xdd, 0xc4, 0x3f, 0x06, 0x21, 0xa0, 0x23, 0x5a, 0x76, /* 0x00000380: .:.......?.!.#Zv */ 0xea, 0xd0, 0xcf, 0x8b, 0x85, 0x5f, 0x16, 0x4b, 0x03, 0x62, 0x21, 0x3a, 0xcc, 0x2d, 0xa8, 0xd0, /* 0x00000390: ....._.K.b!:.-.. */ 0x15, 0x02, 0x81, 0x80, 0x51, 0xf6, 0x89, 0xbb, 0xa6, 0x6b, 0xb4, 0xcb, 0xd0, 0xc1, 0x27, 0xda, /* 0x000003a0: ....Q....k....'. */ 0xdb, 0x6e, 0xf9, 0xd6, 0xf7, 0x62, 0x81, 0xae, 0xc5, 0x72, 0x36, 0x3e, 0x66, 0x17, 0x99, 0xb0, /* 0x000003b0: .n...b...r6>f... */ 0x14, 0xad, 0x52, 0x96, 0x03, 0xf2, 0x1e, 0x41, 0x76, 0x61, 0xb6, 0x3c, 0x02, 0x7d, 0x2a, 0x98, /* 0x000003c0: ..R....Ava.<.}*. */ 0xb4, 0x18, 0x75, 0x38, 0x6b, 0x1d, 0x2b, 0x7f, 0x3a, 0xcf, 0x96, 0xb1, 0xc4, 0xa7, 0xd2, 0x9b, /* 0x000003d0: ..u8k.+.:....... */ 0xd8, 0x1f, 0xb3, 0x64, 0xda, 0x15, 0x9d, 0xca, 0x91, 0x39, 0x48, 0x67, 0x00, 0x9c, 0xd4, 0x99, /* 0x000003e0: ...d.....9Hg.... */ 0xc3, 0x45, 0x5d, 0xf0, 0x09, 0x32, 0xba, 0x21, 0x1e, 0xe2, 0x64, 0xb8, 0x50, 0x03, 0x17, 0xbe, /* 0x000003f0: .E]..2.!..d.P... */ 0xd5, 0xda, 0x6b, 0xce, 0x34, 0xbe, 0x16, 0x03, 0x65, 0x1b, 0x2f, 0xa0, 0xa1, 0x95, 0xc6, 0x8b, /* 0x00000400: ..k.4...e....... */ 0xc2, 0x3c, 0x59, 0x26, 0xbf, 0xb6, 0x07, 0x85, 0x53, 0x2d, 0xb6, 0x36, 0xa3, 0x91, 0xb9, 0xbb, /* 0x00000410: .3._....`u */ 0x02, 0x57, 0x71, 0xb5, 0xed, 0x47, 0x77, 0xda, 0x1a, 0x40, 0x38, 0xab, 0x82, 0xd2, 0x0d, 0xf5, /* 0x00000470: .Wq..Gw..@8..... */ 0x0e, 0x8e, 0xa9, 0x24, 0xdc, 0x30, 0xc9, 0x98, 0xa2, 0x05, 0xcd, 0xca, 0x01, 0xcf, 0xae, 0x1d, /* 0x00000480: ...$.0.......... */ 0xe9, 0x02, 0x47, 0x0e, 0x46, 0x1d, 0x52, 0x02, 0x9a, 0x99, 0x22, 0x23, 0x7f, 0xf8, 0x9e, 0xc2, /* 0x00000490: ..G.F.R..."#.... */ 0x16, 0x86, 0xca, 0xa0, 0xa7, 0x34, 0xfb, 0xbc, /* 0x000004a0: .....4.. */ }; #endif /* RT_OS_WINDOWS */ /** * Certificate w/ public key + private key pair for signing. */ class SignToolKeyPair { protected: /* Context: */ const char *m_pszWhat; bool m_fMandatory; /* Parameters kept till finalizing parsing: */ const char *m_pszCertFile; const char *m_pszCertSha1; uint8_t m_abCertSha1[RTSHA1_HASH_SIZE]; const char *m_pszCertSubject; const char *m_pszCertStore; bool m_fMachineStore; /**< false = personal store */ const char *m_pszKeyFile; const char *m_pszKeyPassword; const char *m_pszKeyName; const char *m_pszKeyProvider; /** String buffer for m_pszKeyPassword when read from file. */ RTCString m_strPassword; /** Storage for pCertificate when it's loaded from a file. */ RTCRX509CERTIFICATE m_DecodedCert; #ifdef RT_OS_WINDOWS /** For the fake certificate */ RTCRX509CERTIFICATE m_DecodedFakeCert; /** The certificate store. */ HCERTSTORE m_hStore; /** The windows certificate context. */ PCCERT_CONTEXT m_pCertCtx; /** Whether hNCryptPrivateKey/hLegacyPrivateKey needs freeing or not. */ BOOL m_fFreePrivateHandle; #endif /** Set if already finalized. */ bool m_fFinalized; /** Store containing the intermediate certificates available to the host. * */ static RTCRSTORE s_hStoreIntermediate; /** Instance counter for helping cleaning up m_hStoreIntermediate. */ static uint32_t s_cInstances; public: /* used to be a struct, thus not prefix either. */ /* Result: */ PCRTCRX509CERTIFICATE pCertificate; RTCRKEY hPrivateKey; #ifdef RT_OS_WINDOWS PCRTCRX509CERTIFICATE pCertificateReal; NCRYPT_KEY_HANDLE hNCryptPrivateKey; HCRYPTPROV hLegacyPrivateKey; #endif public: SignToolKeyPair(const char *a_pszWhat, bool a_fMandatory = false) : m_pszWhat(a_pszWhat) , m_fMandatory(a_fMandatory) , m_pszCertFile(NULL) , m_pszCertSha1(NULL) , m_pszCertSubject(NULL) , m_pszCertStore("MY") , m_fMachineStore(false) , m_pszKeyFile(NULL) , m_pszKeyPassword(NULL) , m_pszKeyName(NULL) , m_pszKeyProvider(NULL) #ifdef RT_OS_WINDOWS , m_hStore(NULL) , m_pCertCtx(NULL) , m_fFreePrivateHandle(FALSE) #endif , m_fFinalized(false) , pCertificate(NULL) , hPrivateKey(NIL_RTCRKEY) #ifdef RT_OS_WINDOWS , pCertificateReal(NULL) , hNCryptPrivateKey(0) , hLegacyPrivateKey(0) #endif { RT_ZERO(m_DecodedCert); #ifdef RT_OS_WINDOWS RT_ZERO(m_DecodedFakeCert); #endif s_cInstances++; } virtual ~SignToolKeyPair() { if (hPrivateKey != NIL_RTCRKEY) { RTCrKeyRelease(hPrivateKey); hPrivateKey = NIL_RTCRKEY; } if (pCertificate == &m_DecodedCert) { RTCrX509Certificate_Delete(&m_DecodedCert); pCertificate = NULL; } #ifdef RT_OS_WINDOWS if (pCertificate == &m_DecodedFakeCert) { RTCrX509Certificate_Delete(&m_DecodedFakeCert); RTCrX509Certificate_Delete(&m_DecodedCert); pCertificate = NULL; pCertificateReal = NULL; } #endif #ifdef RT_OS_WINDOWS if (m_pCertCtx != NULL) { CertFreeCertificateContext(m_pCertCtx); m_pCertCtx = NULL; } if (m_hStore != NULL) { CertCloseStore(m_hStore, 0); m_hStore = NULL; } #endif s_cInstances--; if (s_cInstances == 0) { RTCrStoreRelease(s_hStoreIntermediate); s_hStoreIntermediate = NIL_RTCRSTORE; } } bool isComplete(void) const { return pCertificate && hPrivateKey != NIL_RTCRKEY; } bool isNull(void) const { return pCertificate == NULL && hPrivateKey == NIL_RTCRKEY; } RTEXITCODE handleOption(unsigned offOpt, PRTGETOPTUNION pValueUnion) { AssertReturn(!m_fFinalized, RTMsgErrorExitFailure("Cannot handle options after finalizeOptions was called!")); switch (offOpt) { case OPT_OFF_CERT_FILE: m_pszCertFile = pValueUnion->psz; m_pszCertSha1 = NULL; m_pszCertSubject = NULL; break; case OPT_OFF_CERT_SHA1: { /* Crude normalization of input separators to colons, since it's likely to use spaces and our conversion function only does colons or nothing. */ char szDigest[RTSHA1_DIGEST_LEN * 3 + 1]; int rc = RTStrCopy(szDigest, sizeof(szDigest), pValueUnion->psz); if (RT_SUCCESS(rc)) { char *pszDigest = RTStrStrip(szDigest); size_t offDst = 0; size_t offSrc = 0; char ch; while ((ch = pszDigest[offSrc++]) != '\0') { if (ch == ' ' || ch == '\t' || ch == ':') { while ((ch = pszDigest[offSrc]) == ' ' || ch == '\t' || ch == ':') offSrc++; ch = ch ? ':' : '\0'; } pszDigest[offDst++] = ch; } pszDigest[offDst] = '\0'; /** @todo add a more relaxed input mode to RTStrConvertHexBytes that can deal * with spaces as well as multi-byte cluster of inputs. */ rc = RTStrConvertHexBytes(pszDigest, m_abCertSha1, RTSHA1_HASH_SIZE, RTSTRCONVERTHEXBYTES_F_SEP_COLON); if (RT_SUCCESS(rc)) { m_pszCertFile = NULL; m_pszCertSha1 = pValueUnion->psz; m_pszCertSubject = NULL; break; } } return RTMsgErrorExitFailure("malformed SHA-1 certificate fingerprint (%Rrc): %s", rc, pValueUnion->psz); } case OPT_OFF_CERT_SUBJECT: m_pszCertFile = NULL; m_pszCertSha1 = NULL; m_pszCertSubject = pValueUnion->psz; break; case OPT_OFF_CERT_STORE: m_pszCertStore = pValueUnion->psz; break; case OPT_OFF_CERT_STORE_MACHINE: m_fMachineStore = true; break; case OPT_OFF_KEY_FILE: m_pszKeyFile = pValueUnion->psz; m_pszKeyName = NULL; break; case OPT_OFF_KEY_NAME: m_pszKeyFile = NULL; m_pszKeyName = pValueUnion->psz; break; case OPT_OFF_KEY_PROVIDER: m_pszKeyProvider = pValueUnion->psz; break; case OPT_OFF_KEY_PASSWORD: m_pszKeyPassword = pValueUnion->psz; break; case OPT_OFF_KEY_PASSWORD_FILE: { m_pszKeyPassword = NULL; size_t const cchMax = 512; int rc = m_strPassword.reserveNoThrow(cchMax + 1); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("out of memory"); PRTSTREAM pStrm = g_pStdIn; bool const fClose = strcmp(pValueUnion->psz, "stdin") != 0; if (fClose) { rc = RTStrmOpen(pValueUnion->psz, "r", &pStrm); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("Failed to open password file '%s' for reading: %Rrc", pValueUnion->psz, rc); } rc = RTStrmGetLine(pStrm, m_strPassword.mutableRaw(), cchMax); if (fClose) RTStrmClose(pStrm); if (rc == VERR_BUFFER_OVERFLOW || rc == VINF_BUFFER_OVERFLOW) return RTMsgErrorExitFailure("Password from '%s' is too long (max %zu)", pValueUnion->psz, cchMax); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("Error reading password from '%s': %Rrc", pValueUnion->psz, rc); m_strPassword.jolt(); m_strPassword.stripRight(); m_pszKeyPassword = m_strPassword.c_str(); break; } default: AssertFailedReturn(RTMsgErrorExitFailure("Invalid offOpt=%u!\n", offOpt)); } return RTEXITCODE_SUCCESS; } RTEXITCODE finalizeOptions(unsigned cVerbosity) { RT_NOREF(cVerbosity); /* Only do this once. */ if (m_fFinalized) return RTEXITCODE_SUCCESS; m_fFinalized = true; /* * Got a cert? Is it required? */ bool const fHasKey = ( m_pszKeyFile != NULL || m_pszKeyName != NULL); bool const fHasCert = ( m_pszCertFile != NULL || m_pszCertSha1 != NULL || m_pszCertSubject != NULL); if (!fHasCert) { if (m_fMandatory) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Specifying a %s certificiate is required.", m_pszWhat); return RTEXITCODE_SUCCESS; } /* * Get the certificate. */ RTERRINFOSTATIC ErrInfo; /* From file: */ if (m_pszCertFile) { int rc = RTCrX509Certificate_ReadFromFile(&m_DecodedCert, m_pszCertFile, 0, &g_RTAsn1DefaultAllocator, RTErrInfoInitStatic(&ErrInfo)); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("Error reading %s certificate from '%s': %Rrc%#RTeim", m_pszWhat, m_pszCertFile, rc, &ErrInfo.Core); pCertificate = &m_DecodedCert; } /* From certificate store by name (substring) or fingerprint: */ else { #ifdef RT_OS_WINDOWS m_hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM_A, X509_ASN_ENCODING, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG | CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_ENUM_ARCHIVED_FLAG | (m_fMachineStore ? CERT_SYSTEM_STORE_LOCAL_MACHINE : CERT_SYSTEM_STORE_CURRENT_USER), m_pszCertStore); if (m_hStore == NULL) return RTMsgErrorExitFailure("Failed to open %s store '%s': %Rwc (%u)", m_fMachineStore ? "machine" : "user", m_pszCertStore, GetLastError(), GetLastError()); CRYPT_HASH_BLOB Thumbprint = { RTSHA1_HASH_SIZE, m_abCertSha1 }; PRTUTF16 pwszSubject = NULL; void const *pvFindParam = &Thumbprint; DWORD fFind = CERT_FIND_SHA1_HASH; if (!m_pszCertSha1) { int rc = RTStrToUtf16(m_pszCertSubject, &pwszSubject); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTStrToUtf16 failed: %Rrc, input %.*Rhxs", rc, strlen(m_pszCertSubject), m_pszCertSubject); pvFindParam = pwszSubject; fFind = CERT_FIND_SUBJECT_STR; } while ((m_pCertCtx = CertFindCertificateInStore(m_hStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0 /*fFlags*/, fFind, pvFindParam, m_pCertCtx)) != NULL) { if (m_pCertCtx->dwCertEncodingType & X509_ASN_ENCODING) { RTASN1CURSORPRIMARY PrimaryCursor; RTAsn1CursorInitPrimary(&PrimaryCursor, m_pCertCtx->pbCertEncoded, m_pCertCtx->cbCertEncoded, RTErrInfoInitStatic(&ErrInfo), &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, "CurCtx"); int rc = RTCrX509Certificate_DecodeAsn1(&PrimaryCursor.Cursor, 0, &m_DecodedCert, "Cert"); if (RT_SUCCESS(rc)) { pCertificate = &m_DecodedCert; break; } RTMsgError("failed to decode certificate %p: %Rrc%#RTeim", m_pCertCtx, rc, &ErrInfo.Core); } } RTUtf16Free(pwszSubject); if (!m_pCertCtx) return RTMsgErrorExitFailure("No certificate found matching %s '%s' (%Rwc / %u)", m_pszCertSha1 ? "thumbprint" : "subject substring", m_pszCertSha1 ? m_pszCertSha1 : m_pszCertSubject, GetLastError(), GetLastError()); /* Use this for private key too? */ if (!fHasKey) { HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hTmpPrivateKey = 0; DWORD dwKeySpec = 0; if (CryptAcquireCertificatePrivateKey(m_pCertCtx, CRYPT_ACQUIRE_SILENT_FLAG | CRYPT_ACQUIRE_COMPARE_KEY_FLAG | CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG | CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, NULL, &hTmpPrivateKey, &dwKeySpec, &m_fFreePrivateHandle)) { if (cVerbosity > 1) RTMsgInfo("hTmpPrivateKey=%p m_fFreePrivateHandle=%d dwKeySpec=%#x", hTmpPrivateKey, m_fFreePrivateHandle, dwKeySpec); Assert(dwKeySpec == CERT_NCRYPT_KEY_SPEC); if (dwKeySpec == CERT_NCRYPT_KEY_SPEC) hNCryptPrivateKey = hTmpPrivateKey; else hLegacyPrivateKey = hTmpPrivateKey; /** @todo remove or drop CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG */ return loadFakePrivateKeyAndCert(); } return RTMsgErrorExitFailure("CryptAcquireCertificatePrivateKey failed: %Rwc (%d)", GetLastError(), GetLastError()); } #else return RTMsgErrorExitFailure("Certificate store support is missing on this host"); #endif } /* * Get hold of the private key (if someone above already did, they'd returned already). */ Assert(hPrivateKey == NIL_RTCRKEY); /* Use cert file if nothing else specified. */ if (!fHasKey && m_pszCertFile) m_pszKeyFile = m_pszCertFile; /* Load from file:*/ if (m_pszKeyFile) { int rc = RTCrKeyCreateFromFile(&hPrivateKey, 0 /*fFlags*/, m_pszKeyFile, m_pszKeyPassword, RTErrInfoInitStatic(&ErrInfo)); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("Error reading the %s private key from '%s': %Rrc%#RTeim", m_pszWhat, m_pszKeyFile, rc, &ErrInfo.Core); } /* From key store: */ else { return RTMsgErrorExitFailure("Key store support is missing on this host"); } return RTEXITCODE_SUCCESS; } /** Returns the real certificate. */ PCRTCRX509CERTIFICATE getRealCertificate() const { #ifdef RT_OS_WINDOWS if (pCertificateReal) return pCertificateReal; #endif return pCertificate; } #ifdef RT_OS_WINDOWS RTEXITCODE loadFakePrivateKeyAndCert() { int rc = RTCrX509Certificate_ReadFromBuffer(&m_DecodedFakeCert, g_abFakeCertificate, sizeof(g_abFakeCertificate), 0 /*fFlags*/, &g_RTAsn1DefaultAllocator, NULL, NULL); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrX509Certificate_ReadFromBuffer/g_abFakeCertificate failed: %Rrc", rc); pCertificateReal = pCertificate; pCertificate = &m_DecodedFakeCert; rc = RTCrKeyCreateFromBuffer(&hPrivateKey, 0 /*fFlags*/, g_abFakeRsaKey, sizeof(g_abFakeRsaKey), NULL, NULL, NULL); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrKeyCreateFromBuffer/g_abFakeRsaKey failed: %Rrc", rc); return RTEXITCODE_SUCCESS; } #endif /** * Search for intermediate CA. * * Currently this only do a single certificate path, so this may go south if * there are multiple paths available. It may work fine for a cross signing * path, as long as the cross over is at the level immediately below the root. */ PCRTCRCERTCTX findNextIntermediateCert(PCRTCRCERTCTX pPrev) { /* * Make sure the store is loaded before we start. */ if (s_hStoreIntermediate == NIL_RTCRSTORE) { Assert(!pPrev); RTERRINFOSTATIC ErrInfo; int rc = RTCrStoreCreateSnapshotById(&s_hStoreIntermediate, !m_fMachineStore ? RTCRSTOREID_USER_INTERMEDIATE_CAS : RTCRSTOREID_SYSTEM_INTERMEDIATE_CAS, RTErrInfoInitStatic(&ErrInfo)); if (RT_FAILURE(rc)) { RTMsgError("RTCrStoreCreateSnapshotById/%s-intermediate-CAs failed: %Rrc%#RTeim", m_fMachineStore ? "user" : "machine", rc, &ErrInfo.Core); return NULL; } } /* * Open the search handle for the parent of the previous/end certificate. * * We don't need to consider RTCRCERTCTX::pTaInfo here as we're not * after trust anchors, only intermediate certificates. */ #ifdef RT_OS_WINDOWS PCRTCRX509CERTIFICATE pChildCert = pPrev ? pPrev->pCert : pCertificateReal ? pCertificateReal : pCertificate; #else PCRTCRX509CERTIFICATE pChildCert = pPrev ? pPrev->pCert : pCertificate; #endif AssertReturnStmt(pChildCert, RTCrCertCtxRelease(pPrev), NULL); RTCRSTORECERTSEARCH Search; int rc = RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280(s_hStoreIntermediate, &pChildCert->TbsCertificate.Issuer, &Search); if (RT_FAILURE(rc)) { RTMsgError("RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280 failed: %Rrc", rc); return NULL; } /* * We only gave the subject so, we have to check the serial number our selves. */ PCRTCRCERTCTX pCertCtx; while ((pCertCtx = RTCrStoreCertSearchNext(s_hStoreIntermediate, &Search)) != NULL) { if ( pCertCtx->pCert && RTAsn1BitString_Compare(&pCertCtx->pCert->TbsCertificate.T1.IssuerUniqueId, &pChildCert->TbsCertificate.T1.IssuerUniqueId) == 0 /* compares presentness too */ && !RTCrX509Certificate_IsSelfSigned(pCertCtx->pCert)) { break; /** @todo compare valid periode too and keep a best match when outside the desired period? */ } RTCrCertCtxRelease(pCertCtx); } RTCrStoreCertSearchDestroy(s_hStoreIntermediate, & Search); RTCrCertCtxRelease(pPrev); return pCertCtx; } /** * Merges the user specified certificates with the signing certificate and any * intermediate CAs we can find in the system store. * * @returns Merged store, NIL_RTCRSTORE on failure (messaged). * @param hUserSpecifiedCertificates The user certificate store. */ RTCRSTORE assembleAllAdditionalCertificates(RTCRSTORE hUserSpecifiedCertificates) { RTCRSTORE hRetStore; int rc = RTCrStoreCreateInMemEx(&hRetStore, 0, hUserSpecifiedCertificates); if (RT_SUCCESS(rc)) { /* Add the signing certificate: */ RTERRINFOSTATIC ErrInfo; rc = RTCrStoreCertAddX509(hRetStore, RTCRCERTCTX_F_ENC_X509_DER | RTCRCERTCTX_F_ADD_IF_NOT_FOUND, #ifdef RT_OS_WINDOWS (PRTCRX509CERTIFICATE)(pCertificateReal ? pCertificateReal : pCertificate), #else (PRTCRX509CERTIFICATE)pCertificate, #endif RTErrInfoInitStatic(&ErrInfo)); if (RT_SUCCESS(rc)) { /* Add all intermediate CAs certificates we can find. */ PCRTCRCERTCTX pInterCaCert = NULL; while ((pInterCaCert = findNextIntermediateCert(pInterCaCert)) != NULL) { rc = RTCrStoreCertAddEncoded(hRetStore, RTCRCERTCTX_F_ENC_X509_DER | RTCRCERTCTX_F_ADD_IF_NOT_FOUND, pInterCaCert->pabEncoded, pInterCaCert->cbEncoded, RTErrInfoInitStatic(&ErrInfo)); if (RT_FAILURE(rc)) { RTMsgError("RTCrStoreCertAddEncoded/InterCA failed: %Rrc%#RTeim", rc, &ErrInfo.Core); RTCrCertCtxRelease(pInterCaCert); break; } } if (RT_SUCCESS(rc)) return hRetStore; } else RTMsgError("RTCrStoreCertAddX509/signer failed: %Rrc%#RTeim", rc, &ErrInfo.Core); RTCrStoreRelease(hRetStore); } else RTMsgError("RTCrStoreCreateInMemEx failed: %Rrc", rc); return NIL_RTCRSTORE; } }; /*static*/ RTCRSTORE SignToolKeyPair::s_hStoreIntermediate = NIL_RTCRSTORE; /*static*/ uint32_t SignToolKeyPair::s_cInstances = 0; /********************************************************************************************************************************* * *********************************************************************************************************************************/ /** Timestamp type. */ typedef enum { /** Old timestamp style. * This is just a counter signature with a trustworthy SigningTime attribute. * Specificially it's the SignerInfo part of a detached PKCS#7 covering the * SignerInfo.EncryptedDigest. */ kTimestampType_Old = 1, /** This is a whole PKCS#7 signature of an TSTInfo from RFC-3161 (see page 7). * Currently not supported. */ kTimestampType_New } TIMESTAMPTYPE; /** * Timestamping options. * * Certificate w/ public key + private key pair for signing and signature type. */ class SignToolTimestampOpts : public SignToolKeyPair { public: /** Type timestamp type. */ TIMESTAMPTYPE m_enmType; SignToolTimestampOpts(const char *a_pszWhat, TIMESTAMPTYPE a_enmType = kTimestampType_Old) : SignToolKeyPair(a_pszWhat) , m_enmType(a_enmType) { } bool isOldType() const { return m_enmType == kTimestampType_Old; } bool isNewType() const { return m_enmType == kTimestampType_New; } }; /********************************************************************************************************************************* * Crypto Store Auto Cleanup Wrapper. * *********************************************************************************************************************************/ class CryptoStore { public: RTCRSTORE m_hStore; CryptoStore() : m_hStore(NIL_RTCRSTORE) { } ~CryptoStore() { if (m_hStore != NIL_RTCRSTORE) { uint32_t cRefs = RTCrStoreRelease(m_hStore); Assert(cRefs == 0); RT_NOREF(cRefs); m_hStore = NIL_RTCRSTORE; } } /** * Adds one or more certificates from the given file. * * @returns boolean success indicator. */ bool addFromFile(const char *pszFilename, PRTERRINFOSTATIC pStaticErrInfo) { int rc = RTCrStoreCertAddFromFile(this->m_hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR, pszFilename, RTErrInfoInitStatic(pStaticErrInfo)); if (RT_SUCCESS(rc)) { if (RTErrInfoIsSet(&pStaticErrInfo->Core)) RTMsgWarning("Warnings loading certificate '%s': %s", pszFilename, pStaticErrInfo->Core.pszMsg); return true; } RTMsgError("Error loading certificate '%s': %Rrc%#RTeim", pszFilename, rc, &pStaticErrInfo->Core); return false; } /** * Adds trusted self-signed certificates from the system. * * @returns boolean success indicator. * @note The selection is self-signed rather than CAs here so that test signing * certificates will be included. */ bool addSelfSignedRootsFromSystem(PRTERRINFOSTATIC pStaticErrInfo) { CryptoStore Tmp; int rc = RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts(&Tmp.m_hStore, RTErrInfoInitStatic(pStaticErrInfo)); if (RT_SUCCESS(rc)) { RTCRSTORECERTSEARCH Search; rc = RTCrStoreCertFindAll(Tmp.m_hStore, &Search); if (RT_SUCCESS(rc)) { PCRTCRCERTCTX pCertCtx; while ((pCertCtx = RTCrStoreCertSearchNext(Tmp.m_hStore, &Search)) != NULL) { /* Add it if it's a full fledged self-signed certificate, otherwise just skip: */ if ( pCertCtx->pCert && RTCrX509Certificate_IsSelfSigned(pCertCtx->pCert)) { int rc2 = RTCrStoreCertAddEncoded(this->m_hStore, pCertCtx->fFlags | RTCRCERTCTX_F_ADD_IF_NOT_FOUND, pCertCtx->pabEncoded, pCertCtx->cbEncoded, NULL); if (RT_FAILURE(rc2)) RTMsgWarning("RTCrStoreCertAddEncoded failed for a certificate: %Rrc", rc2); } RTCrCertCtxRelease(pCertCtx); } int rc2 = RTCrStoreCertSearchDestroy(Tmp.m_hStore, &Search); AssertRC(rc2); return true; } RTMsgError("RTCrStoreCertFindAll failed: %Rrc", rc); } else RTMsgError("RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts failed: %Rrc%#RTeim", rc, &pStaticErrInfo->Core); return false; } }; /********************************************************************************************************************************* * Workers. * *********************************************************************************************************************************/ /** * Deletes the structure. * * @param pThis The structure to initialize. */ static void SignToolPkcs7_Delete(PSIGNTOOLPKCS7 pThis) { RTCrPkcs7ContentInfo_Delete(&pThis->ContentInfo); pThis->pSignedData = NULL; RTMemFree(pThis->pbBuf); pThis->pbBuf = NULL; pThis->cbBuf = 0; RTMemFree(pThis->pbNewBuf); pThis->pbNewBuf = NULL; pThis->cbNewBuf = 0; } /** * Deletes the structure. * * @param pThis The structure to initialize. */ static void SignToolPkcs7Exe_Delete(PSIGNTOOLPKCS7EXE pThis) { if (pThis->hLdrMod != NIL_RTLDRMOD) { int rc2 = RTLdrClose(pThis->hLdrMod); if (RT_FAILURE(rc2)) RTMsgError("RTLdrClose failed: %Rrc\n", rc2); pThis->hLdrMod = NIL_RTLDRMOD; } SignToolPkcs7_Delete(pThis); } /** * Decodes the PKCS #7 blob pointed to by pThis->pbBuf. * * @returns IPRT status code (error message already shown on failure). * @param pThis The PKCS\#7 signature to decode. * @param fCatalog Set if catalog file, clear if executable. */ static int SignToolPkcs7_Decode(PSIGNTOOLPKCS7 pThis, bool fCatalog) { RTERRINFOSTATIC ErrInfo; RTASN1CURSORPRIMARY PrimaryCursor; RTAsn1CursorInitPrimary(&PrimaryCursor, pThis->pbBuf, (uint32_t)pThis->cbBuf, RTErrInfoInitStatic(&ErrInfo), &g_RTAsn1DefaultAllocator, 0, "WinCert"); int rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, &pThis->ContentInfo, "CI"); if (RT_SUCCESS(rc)) { if (RTCrPkcs7ContentInfo_IsSignedData(&pThis->ContentInfo)) { pThis->pSignedData = pThis->ContentInfo.u.pSignedData; /* * Decode the authenticode bits. */ if (!strcmp(pThis->pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID)) { PRTCRSPCINDIRECTDATACONTENT pIndData = pThis->pSignedData->ContentInfo.u.pIndirectDataContent; Assert(pIndData); /* * Check that things add up. */ rc = RTCrPkcs7SignedData_CheckSanity(pThis->pSignedData, RTCRPKCS7SIGNEDDATA_SANITY_F_AUTHENTICODE | RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT, RTErrInfoInitStatic(&ErrInfo), "SD"); if (RT_SUCCESS(rc)) { rc = RTCrSpcIndirectDataContent_CheckSanityEx(pIndData, pThis->pSignedData, RTCRSPCINDIRECTDATACONTENT_SANITY_F_ONLY_KNOWN_HASH, RTErrInfoInitStatic(&ErrInfo)); if (RT_FAILURE(rc)) RTMsgError("SPC indirect data content sanity check failed for '%s': %Rrc - %s\n", pThis->pszFilename, rc, ErrInfo.szMsg); } else RTMsgError("PKCS#7 sanity check failed for '%s': %Rrc - %s\n", pThis->pszFilename, rc, ErrInfo.szMsg); } else if (!strcmp(pThis->pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID)) { /* apple code signing */ } else if (!fCatalog) RTMsgError("Unexpected the signed content in '%s': %s (expected %s)", pThis->pszFilename, pThis->pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID); } else rc = RTMsgErrorRc(VERR_CR_PKCS7_NOT_SIGNED_DATA, "PKCS#7 content is inside '%s' is not 'signedData': %s\n", pThis->pszFilename, pThis->ContentInfo.ContentType.szObjId); } else RTMsgError("RTCrPkcs7ContentInfo_DecodeAsn1 failed on '%s': %Rrc - %s\n", pThis->pszFilename, rc, ErrInfo.szMsg); return rc; } /** * Reads and decodes PKCS\#7 signature from the given cat file. * * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error message * on failure. * @param pThis The structure to initialize. * @param pszFilename The catalog (or any other DER PKCS\#7) filename. * @param cVerbosity The verbosity. */ static RTEXITCODE SignToolPkcs7_InitFromFile(PSIGNTOOLPKCS7 pThis, const char *pszFilename, unsigned cVerbosity) { /* * Init the return structure. */ RT_ZERO(*pThis); pThis->pszFilename = pszFilename; pThis->enmType = RTSIGNTOOLFILETYPE_CAT; /* * Lazy bird uses RTFileReadAll and duplicates the allocation. */ void *pvFile; int rc = RTFileReadAll(pszFilename, &pvFile, &pThis->cbBuf); if (RT_SUCCESS(rc)) { pThis->pbBuf = (uint8_t *)RTMemDup(pvFile, pThis->cbBuf); RTFileReadAllFree(pvFile, pThis->cbBuf); if (pThis->pbBuf) { if (cVerbosity > 2) RTPrintf("PKCS#7 signature: %u bytes\n", pThis->cbBuf); /* * Decode it. */ rc = SignToolPkcs7_Decode(pThis, true /*fCatalog*/); if (RT_SUCCESS(rc)) return RTEXITCODE_SUCCESS; } else RTMsgError("Out of memory!"); } else RTMsgError("Error reading '%s' into memory: %Rrc", pszFilename, rc); SignToolPkcs7_Delete(pThis); return RTEXITCODE_FAILURE; } /** * Encodes the signature into the SIGNTOOLPKCS7::pbNewBuf and * SIGNTOOLPKCS7::cbNewBuf members. * * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error message * on failure. * @param pThis The signature to encode. * @param cVerbosity The verbosity. */ static RTEXITCODE SignToolPkcs7_Encode(PSIGNTOOLPKCS7 pThis, unsigned cVerbosity) { RTERRINFOSTATIC StaticErrInfo; PRTASN1CORE pRoot = RTCrPkcs7ContentInfo_GetAsn1Core(&pThis->ContentInfo); uint32_t cbEncoded; int rc = RTAsn1EncodePrepare(pRoot, RTASN1ENCODE_F_DER, &cbEncoded, RTErrInfoInitStatic(&StaticErrInfo)); if (RT_SUCCESS(rc)) { if (cVerbosity >= 4) RTAsn1Dump(pRoot, 0, 0, RTStrmDumpPrintfV, g_pStdOut); RTMemFree(pThis->pbNewBuf); pThis->cbNewBuf = cbEncoded; pThis->pbNewBuf = (uint8_t *)RTMemAllocZ(cbEncoded); if (pThis->pbNewBuf) { rc = RTAsn1EncodeToBuffer(pRoot, RTASN1ENCODE_F_DER, pThis->pbNewBuf, pThis->cbNewBuf, RTErrInfoInitStatic(&StaticErrInfo)); if (RT_SUCCESS(rc)) { if (cVerbosity > 1) RTMsgInfo("Encoded signature to %u bytes", cbEncoded); return RTEXITCODE_SUCCESS; } RTMsgError("RTAsn1EncodeToBuffer failed: %Rrc", rc); RTMemFree(pThis->pbNewBuf); pThis->pbNewBuf = NULL; } else RTMsgError("Failed to allocate %u bytes!", cbEncoded); } else RTMsgError("RTAsn1EncodePrepare failed: %Rrc - %s", rc, StaticErrInfo.szMsg); return RTEXITCODE_FAILURE; } /** * Helper that makes sure the UnauthenticatedAttributes are present in the given * SignerInfo structure. * * Call this before trying to modify the array. * * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error already * displayed on failure. * @param pSignerInfo The SignerInfo structure in question. */ static RTEXITCODE SignToolPkcs7_EnsureUnauthenticatedAttributesPresent(PRTCRPKCS7SIGNERINFO pSignerInfo) { if (pSignerInfo->UnauthenticatedAttributes.cItems == 0) { /* HACK ALERT! Invent ASN.1 setters/whatever for members to replace this mess. */ if (pSignerInfo->AuthenticatedAttributes.cItems == 0) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No authenticated or unauthenticated attributes! Sorry, no can do."); Assert(pSignerInfo->UnauthenticatedAttributes.SetCore.Asn1Core.uTag == 0); int rc = RTAsn1SetCore_Init(&pSignerInfo->UnauthenticatedAttributes.SetCore, pSignerInfo->AuthenticatedAttributes.SetCore.Asn1Core.pOps); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTAsn1SetCore_Init failed: %Rrc", rc); pSignerInfo->UnauthenticatedAttributes.SetCore.Asn1Core.uTag = 1; pSignerInfo->UnauthenticatedAttributes.SetCore.Asn1Core.fClass = ASN1_TAGCLASS_CONTEXT | ASN1_TAGFLAG_CONSTRUCTED; RTAsn1MemInitArrayAllocation(&pSignerInfo->UnauthenticatedAttributes.Allocation, pSignerInfo->AuthenticatedAttributes.Allocation.pAllocator, sizeof(**pSignerInfo->UnauthenticatedAttributes.papItems)); } return RTEXITCODE_SUCCESS; } /** * Adds the @a pSrc signature as a nested signature. * * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error message * on failure. * @param pThis The signature to modify. * @param pSrc The signature to add as nested. * @param cVerbosity The verbosity. * @param fPrepend Whether to prepend (true) or append (false) the * source signature to the nested attribute. */ static RTEXITCODE SignToolPkcs7_AddNestedSignature(PSIGNTOOLPKCS7 pThis, PSIGNTOOLPKCS7 pSrc, unsigned cVerbosity, bool fPrepend) { PRTCRPKCS7SIGNERINFO pSignerInfo = pThis->pSignedData->SignerInfos.papItems[0]; /* * Deal with UnauthenticatedAttributes being absent before trying to append to the array. */ RTEXITCODE rcExit = SignToolPkcs7_EnsureUnauthenticatedAttributesPresent(pSignerInfo); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; /* * Find or add an unauthenticated attribute for nested signatures. */ int rc = VERR_NOT_FOUND; PRTCRPKCS7ATTRIBUTE pAttr = NULL; int32_t iPos = pSignerInfo->UnauthenticatedAttributes.cItems; while (iPos-- > 0) if (pSignerInfo->UnauthenticatedAttributes.papItems[iPos]->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE) { pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iPos]; rc = VINF_SUCCESS; break; } if (iPos < 0) { iPos = RTCrPkcs7Attributes_Append(&pSignerInfo->UnauthenticatedAttributes); if (iPos >= 0) { if (cVerbosity >= 3) RTMsgInfo("Adding UnauthenticatedAttribute #%u...", iPos); Assert((uint32_t)iPos < pSignerInfo->UnauthenticatedAttributes.cItems); pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iPos]; rc = RTAsn1ObjId_InitFromString(&pAttr->Type, RTCR_PKCS9_ID_MS_NESTED_SIGNATURE, pAttr->Allocation.pAllocator); if (RT_SUCCESS(rc)) { /** @todo Generalize the Type + enmType DYN stuff and generate setters. */ Assert(pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_NOT_PRESENT); Assert(pAttr->uValues.pContentInfos == NULL); pAttr->enmType = RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE; rc = RTAsn1MemAllocZ(&pAttr->Allocation, (void **)&pAttr->uValues.pContentInfos, sizeof(*pAttr->uValues.pContentInfos)); if (RT_SUCCESS(rc)) { rc = RTCrPkcs7SetOfContentInfos_Init(pAttr->uValues.pContentInfos, pAttr->Allocation.pAllocator); if (!RT_SUCCESS(rc)) RTMsgError("RTCrPkcs7ContentInfos_Init failed: %Rrc", rc); } else RTMsgError("RTAsn1MemAllocZ failed: %Rrc", rc); } else RTMsgError("RTAsn1ObjId_InitFromString failed: %Rrc", rc); } else RTMsgError("RTCrPkcs7Attributes_Append failed: %Rrc", iPos); } else if (cVerbosity >= 2) RTMsgInfo("Found UnauthenticatedAttribute #%u...", iPos); if (RT_SUCCESS(rc)) { /* * Append/prepend the signature. */ uint32_t iActualPos = UINT32_MAX; iPos = fPrepend ? 0 : pAttr->uValues.pContentInfos->cItems; rc = RTCrPkcs7SetOfContentInfos_InsertEx(pAttr->uValues.pContentInfos, iPos, &pSrc->ContentInfo, pAttr->Allocation.pAllocator, &iActualPos); if (RT_SUCCESS(rc)) { if (cVerbosity > 0) RTMsgInfo("Added nested signature (#%u)", iActualPos); if (cVerbosity >= 3) { RTMsgInfo("SingerInfo dump after change:"); RTAsn1Dump(RTCrPkcs7SignerInfo_GetAsn1Core(pSignerInfo), 0, 2, RTStrmDumpPrintfV, g_pStdOut); } return RTEXITCODE_SUCCESS; } RTMsgError("RTCrPkcs7ContentInfos_InsertEx failed: %Rrc", rc); } return RTEXITCODE_FAILURE; } /** * Writes the signature to the file. * * Caller must have called SignToolPkcs7_Encode() prior to this function. * * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error * message on failure. * @param pThis The file which to write. * @param cVerbosity The verbosity. */ static RTEXITCODE SignToolPkcs7_WriteSignatureToFile(PSIGNTOOLPKCS7 pThis, const char *pszFilename, unsigned cVerbosity) { AssertReturn(pThis->cbNewBuf && pThis->pbNewBuf, RTEXITCODE_FAILURE); /* * Open+truncate file, write new signature, close. Simple. */ RTFILE hFile; int rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_WRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_TRUNCATE | RTFILE_O_DENY_WRITE); if (RT_SUCCESS(rc)) { rc = RTFileWrite(hFile, pThis->pbNewBuf, pThis->cbNewBuf, NULL); if (RT_SUCCESS(rc)) { rc = RTFileClose(hFile); if (RT_SUCCESS(rc)) { if (cVerbosity > 0) RTMsgInfo("Wrote %u bytes to %s", pThis->cbNewBuf, pszFilename); return RTEXITCODE_SUCCESS; } RTMsgError("RTFileClose failed on %s: %Rrc", pszFilename, rc); } else RTMsgError("Write error on %s: %Rrc", pszFilename, rc); } else RTMsgError("Failed to open %s for writing: %Rrc", pszFilename, rc); return RTEXITCODE_FAILURE; } /** * Worker for recursively searching for MS nested signatures and signer infos. * * @returns Pointer to the signer info corresponding to @a iReqSignature. NULL * if not found. * @param pSignedData The signature to search. * @param piNextSignature Pointer to the variable keeping track of the next * signature number. * @param iReqSignature The request signature number. * @param ppSignedData Where to return the signature data structure. * Optional. */ static PRTCRPKCS7SIGNERINFO SignToolPkcs7_FindNestedSignatureByIndexWorker(PRTCRPKCS7SIGNEDDATA pSignedData, uint32_t *piNextSignature, uint32_t iReqSignature, PRTCRPKCS7SIGNEDDATA *ppSignedData) { for (uint32_t iSignerInfo = 0; iSignerInfo < pSignedData->SignerInfos.cItems; iSignerInfo++) { /* Match?*/ PRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[iSignerInfo]; if (*piNextSignature == iReqSignature) { if (ppSignedData) *ppSignedData = pSignedData; return pSignerInfo; } *piNextSignature += 1; /* Look for nested signatures. */ for (uint32_t iAttrib = 0; iAttrib < pSignerInfo->UnauthenticatedAttributes.cItems; iAttrib++) if (pSignerInfo->UnauthenticatedAttributes.papItems[iAttrib]->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE) { PRTCRPKCS7SETOFCONTENTINFOS pCntInfos; pCntInfos = pSignerInfo->UnauthenticatedAttributes.papItems[iAttrib]->uValues.pContentInfos; for (uint32_t iCntInfo = 0; iCntInfo < pCntInfos->cItems; iCntInfo++) { PRTCRPKCS7CONTENTINFO pCntInfo = pCntInfos->papItems[iCntInfo]; if (RTCrPkcs7ContentInfo_IsSignedData(pCntInfo)) { PRTCRPKCS7SIGNERINFO pRet; pRet = SignToolPkcs7_FindNestedSignatureByIndexWorker(pCntInfo->u.pSignedData, piNextSignature, iReqSignature, ppSignedData); if (pRet) return pRet; } } } } return NULL; } /** * Locates the given nested signature. * * @returns Pointer to the signer info corresponding to @a iReqSignature. NULL * if not found. * @param pThis The PKCS\#7 structure to search. * @param iReqSignature The requested signature number. * @param ppSignedData Where to return the pointer to the signed data that * the returned signer info belongs to. * * @todo Move into SPC or PKCS\#7. */ static PRTCRPKCS7SIGNERINFO SignToolPkcs7_FindNestedSignatureByIndex(PSIGNTOOLPKCS7 pThis, uint32_t iReqSignature, PRTCRPKCS7SIGNEDDATA *ppSignedData) { uint32_t iNextSignature = 0; return SignToolPkcs7_FindNestedSignatureByIndexWorker(pThis->pSignedData, &iNextSignature, iReqSignature, ppSignedData); } /** * Reads and decodes PKCS\#7 signature from the given executable, if it has one. * * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error message * on failure. * @param pThis The structure to initialize. * @param pszFilename The executable filename. * @param cVerbosity The verbosity. * @param enmLdrArch For FAT binaries. * @param fAllowUnsigned Whether to allow unsigned binaries. */ static RTEXITCODE SignToolPkcs7Exe_InitFromFile(PSIGNTOOLPKCS7EXE pThis, const char *pszFilename, unsigned cVerbosity, RTLDRARCH enmLdrArch = RTLDRARCH_WHATEVER, bool fAllowUnsigned = false) { /* * Init the return structure. */ RT_ZERO(*pThis); pThis->hLdrMod = NIL_RTLDRMOD; pThis->pszFilename = pszFilename; pThis->enmType = RTSIGNTOOLFILETYPE_EXE; /* * Open the image and check if it's signed. */ int rc = RTLdrOpen(pszFilename, RTLDR_O_FOR_VALIDATION, enmLdrArch, &pThis->hLdrMod); if (RT_SUCCESS(rc)) { bool fIsSigned = false; rc = RTLdrQueryProp(pThis->hLdrMod, RTLDRPROP_IS_SIGNED, &fIsSigned, sizeof(fIsSigned)); if (RT_SUCCESS(rc) && fIsSigned) { /* * Query the PKCS#7 data (assuming M$ style signing) and hand it to a worker. */ size_t cbActual = 0; #ifdef DEBUG size_t cbBuf = 64; #else size_t cbBuf = _512K; #endif void *pvBuf = RTMemAllocZ(cbBuf); if (pvBuf) { rc = RTLdrQueryPropEx(pThis->hLdrMod, RTLDRPROP_PKCS7_SIGNED_DATA, NULL /*pvBits*/, pvBuf, cbBuf, &cbActual); if (rc == VERR_BUFFER_OVERFLOW) { RTMemFree(pvBuf); cbBuf = cbActual; pvBuf = RTMemAllocZ(cbActual); if (pvBuf) rc = RTLdrQueryPropEx(pThis->hLdrMod, RTLDRPROP_PKCS7_SIGNED_DATA, NULL /*pvBits*/, pvBuf, cbBuf, &cbActual); else rc = VERR_NO_MEMORY; } } else rc = VERR_NO_MEMORY; pThis->pbBuf = (uint8_t *)pvBuf; pThis->cbBuf = cbActual; if (RT_SUCCESS(rc)) { if (cVerbosity > 2) RTPrintf("PKCS#7 signature: %u bytes\n", cbActual); if (cVerbosity > 3) RTPrintf("%.*Rhxd\n", cbActual, pvBuf); /* * Decode it. */ rc = SignToolPkcs7_Decode(pThis, false /*fCatalog*/); if (RT_SUCCESS(rc)) return RTEXITCODE_SUCCESS; } else RTMsgError("RTLdrQueryPropEx/RTLDRPROP_PKCS7_SIGNED_DATA failed on '%s': %Rrc\n", pszFilename, rc); } else if (RT_SUCCESS(rc)) { if (!fAllowUnsigned || cVerbosity >= 2) RTMsgInfo("'%s': not signed\n", pszFilename); if (fAllowUnsigned) return RTEXITCODE_SUCCESS; } else RTMsgError("RTLdrQueryProp/RTLDRPROP_IS_SIGNED failed on '%s': %Rrc\n", pszFilename, rc); } else RTMsgError("Error opening executable image '%s': %Rrc", pszFilename, rc); SignToolPkcs7Exe_Delete(pThis); return RTEXITCODE_FAILURE; } /** * Calculates the checksum of an executable. * * @returns Success indicator (errors are reported) * @param pThis The exe file to checksum. * @param hFile The file handle. * @param puCheckSum Where to return the checksum. */ static bool SignToolPkcs7Exe_CalcPeCheckSum(PSIGNTOOLPKCS7EXE pThis, RTFILE hFile, uint32_t *puCheckSum) { #ifdef RT_OS_WINDOWS /* * Try use IMAGEHLP!MapFileAndCheckSumW first. */ PRTUTF16 pwszPath; int rc = RTStrToUtf16(pThis->pszFilename, &pwszPath); if (RT_SUCCESS(rc)) { decltype(MapFileAndCheckSumW) *pfnMapFileAndCheckSumW; pfnMapFileAndCheckSumW = (decltype(MapFileAndCheckSumW) *)RTLdrGetSystemSymbol("IMAGEHLP.DLL", "MapFileAndCheckSumW"); if (pfnMapFileAndCheckSumW) { DWORD uOldSum = UINT32_MAX; DWORD uCheckSum = UINT32_MAX; DWORD dwRc = pfnMapFileAndCheckSumW(pwszPath, &uOldSum, &uCheckSum); if (dwRc == CHECKSUM_SUCCESS) { *puCheckSum = uCheckSum; return true; } } } #endif RT_NOREF(pThis, hFile, puCheckSum); RTMsgError("Implement check sum calcuation fallback!"); return false; } /** * Writes the signature to the file. * * This has the side-effect of closing the hLdrMod member. So, it can only be * called once! * * Caller must have called SignToolPkcs7_Encode() prior to this function. * * @returns RTEXITCODE_SUCCESS on success, RTEXITCODE_FAILURE with error * message on failure. * @param pThis The file which to write. * @param cVerbosity The verbosity. */ static RTEXITCODE SignToolPkcs7Exe_WriteSignatureToFile(PSIGNTOOLPKCS7EXE pThis, unsigned cVerbosity) { AssertReturn(pThis->cbNewBuf && pThis->pbNewBuf, RTEXITCODE_FAILURE); /* * Get the file header offset and arch before closing the destination handle. */ uint32_t offNtHdrs; int rc = RTLdrQueryProp(pThis->hLdrMod, RTLDRPROP_FILE_OFF_HEADER, &offNtHdrs, sizeof(offNtHdrs)); if (RT_SUCCESS(rc)) { RTLDRARCH enmLdrArch = RTLdrGetArch(pThis->hLdrMod); if (enmLdrArch != RTLDRARCH_INVALID) { RTLdrClose(pThis->hLdrMod); pThis->hLdrMod = NIL_RTLDRMOD; unsigned cbNtHdrs = 0; switch (enmLdrArch) { case RTLDRARCH_AMD64: cbNtHdrs = sizeof(IMAGE_NT_HEADERS64); break; case RTLDRARCH_X86_32: cbNtHdrs = sizeof(IMAGE_NT_HEADERS32); break; default: RTMsgError("Unknown image arch: %d", enmLdrArch); } if (cbNtHdrs > 0) { if (cVerbosity > 0) RTMsgInfo("offNtHdrs=%#x cbNtHdrs=%u\n", offNtHdrs, cbNtHdrs); /* * Open the executable file for writing. */ RTFILE hFile; rc = RTFileOpen(&hFile, pThis->pszFilename, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); if (RT_SUCCESS(rc)) { /* Read the file header and locate the security directory entry. */ union { IMAGE_NT_HEADERS32 NtHdrs32; IMAGE_NT_HEADERS64 NtHdrs64; } uBuf; PIMAGE_DATA_DIRECTORY pSecDir = cbNtHdrs == sizeof(IMAGE_NT_HEADERS64) ? &uBuf.NtHdrs64.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY] : &uBuf.NtHdrs32.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]; rc = RTFileReadAt(hFile, offNtHdrs, &uBuf, cbNtHdrs, NULL); if ( RT_SUCCESS(rc) && uBuf.NtHdrs32.Signature == IMAGE_NT_SIGNATURE) { /* * Drop any old signature by truncating the file. */ if ( pSecDir->Size > 8 && pSecDir->VirtualAddress > offNtHdrs + sizeof(IMAGE_NT_HEADERS32)) { rc = RTFileSetSize(hFile, pSecDir->VirtualAddress); if (RT_FAILURE(rc)) RTMsgError("Error truncating file to %#x bytes: %Rrc", pSecDir->VirtualAddress, rc); } else if (pSecDir->Size != 0 && pSecDir->VirtualAddress == 0) rc = RTMsgErrorRc(VERR_BAD_EXE_FORMAT, "Bad security directory entry: VA=%#x Size=%#x", pSecDir->VirtualAddress, pSecDir->Size); if (RT_SUCCESS(rc)) { /* * Pad the file with zero up to a WIN_CERTIFICATE_ALIGNMENT boundary. * * Since the hash algorithm hashes everything up to the signature data, * zero padding included, the alignment we do here must match the alignment * padding that done while calculating the hash. */ uint32_t const cbWinCert = RT_UOFFSETOF(WIN_CERTIFICATE, bCertificate); uint64_t offCur = 0; rc = RTFileQuerySize(hFile, &offCur); if ( RT_SUCCESS(rc) && offCur < _2G) { if (offCur != RT_ALIGN_64(offCur, WIN_CERTIFICATE_ALIGNMENT)) { uint32_t const cbNeeded = (uint32_t)(RT_ALIGN_64(offCur, WIN_CERTIFICATE_ALIGNMENT) - offCur); rc = RTFileWriteAt(hFile, offCur, g_abRTZero4K, cbNeeded, NULL); if (RT_SUCCESS(rc)) offCur += cbNeeded; } if (RT_SUCCESS(rc)) { /* * Write the header followed by the signature data. */ uint32_t const cbZeroPad = (uint32_t)(RT_ALIGN_Z(pThis->cbNewBuf, 8) - pThis->cbNewBuf); pSecDir->VirtualAddress = (uint32_t)offCur; pSecDir->Size = cbWinCert + (uint32_t)pThis->cbNewBuf + cbZeroPad; if (cVerbosity >= 2) RTMsgInfo("Writing %u (%#x) bytes of signature at %#x (%u).\n", pSecDir->Size, pSecDir->Size, pSecDir->VirtualAddress, pSecDir->VirtualAddress); WIN_CERTIFICATE WinCert; WinCert.dwLength = pSecDir->Size; WinCert.wRevision = WIN_CERT_REVISION_2_0; WinCert.wCertificateType = WIN_CERT_TYPE_PKCS_SIGNED_DATA; rc = RTFileWriteAt(hFile, offCur, &WinCert, cbWinCert, NULL); if (RT_SUCCESS(rc)) { offCur += cbWinCert; rc = RTFileWriteAt(hFile, offCur, pThis->pbNewBuf, pThis->cbNewBuf, NULL); } if (RT_SUCCESS(rc) && cbZeroPad) { offCur += pThis->cbNewBuf; rc = RTFileWriteAt(hFile, offCur, g_abRTZero4K, cbZeroPad, NULL); } if (RT_SUCCESS(rc)) { /* * Reset the checksum (sec dir updated already) and rewrite the header. */ uBuf.NtHdrs32.OptionalHeader.CheckSum = 0; offCur = offNtHdrs; rc = RTFileWriteAt(hFile, offNtHdrs, &uBuf, cbNtHdrs, NULL); if (RT_SUCCESS(rc)) rc = RTFileFlush(hFile); if (RT_SUCCESS(rc)) { /* * Calc checksum and write out the header again. */ uint32_t uCheckSum = UINT32_MAX; if (SignToolPkcs7Exe_CalcPeCheckSum(pThis, hFile, &uCheckSum)) { uBuf.NtHdrs32.OptionalHeader.CheckSum = uCheckSum; rc = RTFileWriteAt(hFile, offNtHdrs, &uBuf, cbNtHdrs, NULL); if (RT_SUCCESS(rc)) rc = RTFileFlush(hFile); if (RT_SUCCESS(rc)) { rc = RTFileClose(hFile); if (RT_SUCCESS(rc)) return RTEXITCODE_SUCCESS; RTMsgError("RTFileClose failed: %Rrc\n", rc); return RTEXITCODE_FAILURE; } } } } } if (RT_FAILURE(rc)) RTMsgError("Write error at %#RX64: %Rrc", offCur, rc); } else if (RT_SUCCESS(rc)) RTMsgError("File to big: %'RU64 bytes", offCur); else RTMsgError("RTFileQuerySize failed: %Rrc", rc); } } else if (RT_SUCCESS(rc)) RTMsgError("Not NT executable header!"); else RTMsgError("Error reading NT headers (%#x bytes) at %#x: %Rrc", cbNtHdrs, offNtHdrs, rc); RTFileClose(hFile); } else RTMsgError("Failed to open '%s' for writing: %Rrc", pThis->pszFilename, rc); } } else RTMsgError("RTLdrGetArch failed!"); } else RTMsgError("RTLdrQueryProp/RTLDRPROP_FILE_OFF_HEADER failed: %Rrc", rc); return RTEXITCODE_FAILURE; } #ifndef IPRT_SIGNTOOL_NO_SIGNING static PRTCRPKCS7ATTRIBUTE SignToolPkcs7_AuthAttribAppend(PRTCRPKCS7ATTRIBUTES pAuthAttribs) { int32_t iPos = RTCrPkcs7Attributes_Append(pAuthAttribs); if (iPos >= 0) return pAuthAttribs->papItems[iPos]; RTMsgError("RTCrPkcs7Attributes_Append failed: %Rrc", iPos); return NULL; } static RTEXITCODE SignToolPkcs7_AuthAttribsAddSigningTime(PRTCRPKCS7ATTRIBUTES pAuthAttribs, RTTIMESPEC SigningTime) { /* * Signing time. For the old-style timestamps, Symantec used ASN.1 UTC TIME. * start -vv vv=ASN1_TAG_UTC_TIME * 00000187d6a65fd0/23b0: 0d 01 09 05 31 0f 17 0d-31 36 31 30 30 35 30 37 ....1...16100507 * 00000187d6a65fe0/23c0: 35 30 33 30 5a 30 23 06-09 2a 86 48 86 f7 0d 01 5030Z0#..*.H.... * ^^- end 2016-10-05T07:50:30.000000000Z (161005075030Z) */ PRTCRPKCS7ATTRIBUTE pAttr = SignToolPkcs7_AuthAttribAppend(pAuthAttribs); if (!pAttr) return RTEXITCODE_FAILURE; int rc = RTCrPkcs7Attribute_SetSigningTime(pAttr, NULL, pAuthAttribs->Allocation.pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrPkcs7Attribute_SetSigningTime failed: %Rrc", rc); /* Create the timestamp. */ int32_t iPos = RTAsn1SetOfTimes_Append(pAttr->uValues.pSigningTime); if (iPos < 0) return RTMsgErrorExitFailure("RTAsn1SetOfTimes_Append failed: %Rrc", iPos); PRTASN1TIME pTime = pAttr->uValues.pSigningTime->papItems[iPos]; rc = RTAsn1Time_SetTimeSpec(pTime, pAttr->Allocation.pAllocator, &SigningTime); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTAsn1Time_SetTimeSpec failed: %Rrc", rc); return RTEXITCODE_SUCCESS; } static RTEXITCODE SignToolPkcs7_AuthAttribsAddSpcOpusInfo(PRTCRPKCS7ATTRIBUTES pAuthAttribs, void *pvInfo) { /** @todo The OpusInfo is a structure with an optional SpcString and an * optional SpcLink (url). The two attributes can be set using the /d and /du * options of MS signtool.exe, I think. We shouldn't be using them atm. */ PRTCRPKCS7ATTRIBUTE pAttr = SignToolPkcs7_AuthAttribAppend(pAuthAttribs); if (!pAttr) return RTEXITCODE_FAILURE; int rc = RTCrPkcs7Attribute_SetMsStatementType(pAttr, NULL, pAuthAttribs->Allocation.pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrPkcs7Attribute_SetMsStatementType failed: %Rrc", rc); /* Override the ID. */ rc = RTAsn1ObjId_SetFromString(&pAttr->Type, RTCR_PKCS9_ID_MS_SP_OPUS_INFO, pAuthAttribs->Allocation.pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTAsn1ObjId_SetFromString failed: %Rrc", rc); /* Add attribute value entry. */ int32_t iPos = RTAsn1SetOfObjIdSeqs_Append(pAttr->uValues.pObjIdSeqs); if (iPos < 0) return RTMsgErrorExitFailure("RTAsn1SetOfObjIdSeqs_Append failed: %Rrc", iPos); RT_NOREF(pvInfo); Assert(!pvInfo); return RTEXITCODE_SUCCESS; } static RTEXITCODE SignToolPkcs7_AuthAttribsAddMsStatementType(PRTCRPKCS7ATTRIBUTES pAuthAttribs, const char *pszTypeId) { PRTCRPKCS7ATTRIBUTE pAttr = SignToolPkcs7_AuthAttribAppend(pAuthAttribs); if (!pAttr) return RTEXITCODE_FAILURE; int rc = RTCrPkcs7Attribute_SetMsStatementType(pAttr, NULL, pAuthAttribs->Allocation.pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrPkcs7Attribute_SetMsStatementType failed: %Rrc", rc); /* Add attribute value entry. */ int32_t iPos = RTAsn1SetOfObjIdSeqs_Append(pAttr->uValues.pObjIdSeqs); if (iPos < 0) return RTMsgErrorExitFailure("RTAsn1SetOfObjIdSeqs_Append failed: %Rrc", iPos); PRTASN1SEQOFOBJIDS pSeqObjIds = pAttr->uValues.pObjIdSeqs->papItems[iPos]; /* Add a object id to the value. */ RTASN1OBJID ObjIdValue; rc = RTAsn1ObjId_InitFromString(&ObjIdValue, pszTypeId, &g_RTAsn1DefaultAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTAsn1ObjId_InitFromString/%s failed: %Rrc", pszTypeId, rc); rc = RTAsn1SeqOfObjIds_InsertEx(pSeqObjIds, 0 /*iPos*/, &ObjIdValue, &g_RTAsn1DefaultAllocator, NULL); RTAsn1ObjId_Delete(&ObjIdValue); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTAsn1SeqOfObjIds_InsertEx failed: %Rrc", rc); return RTEXITCODE_SUCCESS; } static RTEXITCODE SignToolPkcs7_AuthAttribsAddContentType(PRTCRPKCS7ATTRIBUTES pAuthAttribs, const char *pszContentTypeId) { PRTCRPKCS7ATTRIBUTE pAttr = SignToolPkcs7_AuthAttribAppend(pAuthAttribs); if (!pAttr) return RTEXITCODE_FAILURE; int rc = RTCrPkcs7Attribute_SetContentType(pAttr, NULL, pAuthAttribs->Allocation.pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrPkcs7Attribute_SetContentType failed: %Rrc", rc); /* Add a object id to the value. */ RTASN1OBJID ObjIdValue; rc = RTAsn1ObjId_InitFromString(&ObjIdValue, pszContentTypeId, pAuthAttribs->Allocation.pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTAsn1ObjId_InitFromString/%s failed: %Rrc", pszContentTypeId, rc); rc = RTAsn1SetOfObjIds_InsertEx(pAttr->uValues.pObjIds, 0 /*iPos*/, &ObjIdValue, pAuthAttribs->Allocation.pAllocator, NULL); RTAsn1ObjId_Delete(&ObjIdValue); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTAsn1SetOfObjIds_InsertEx failed: %Rrc", rc); return RTEXITCODE_SUCCESS; } static RTEXITCODE SignToolPkcs7_AddAuthAttribsForTimestamp(PRTCRPKCS7ATTRIBUTES pAuthAttribs, TIMESTAMPTYPE enmTimestampType, RTTIMESPEC SigningTime, PCRTCRX509CERTIFICATE pTimestampCert) { /* * Add content type. */ RTEXITCODE rcExit = SignToolPkcs7_AuthAttribsAddContentType(pAuthAttribs, enmTimestampType == kTimestampType_Old ? RTCR_PKCS7_DATA_OID : RTCRTSPTSTINFO_OID); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; /* * Add signing time. */ rcExit = SignToolPkcs7_AuthAttribsAddSigningTime(pAuthAttribs, SigningTime); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; /* * More later if we want to support fTimestampTypeOld = false perhaps? */ Assert(enmTimestampType == kTimestampType_Old); RT_NOREF(pTimestampCert); return RTEXITCODE_SUCCESS; } static RTEXITCODE SignToolPkcs7_AddAuthAttribsForImageOrCatSignature(PRTCRPKCS7ATTRIBUTES pAuthAttribs, RTTIMESPEC SigningTime, bool fNoSigningTime, const char *pszContentTypeId) { /* * Add SpcOpusInfo. No attribute values. * SEQ start -vv vv- Type ObjId * 1c60: 0e 03 02 1a 05 00 a0 70-30 10 06 0a 2b 06 01 04 .......p0...+... * 1c70: 01 82 37 02 01 0c 31 02-30 00 30 19 06 09 2a 86 ..7...1.0.0...*. * Set Of -^^ ^^- Empty Sequence. */ RTEXITCODE rcExit = SignToolPkcs7_AuthAttribsAddSpcOpusInfo(pAuthAttribs, NULL /*pvInfo - none*/); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; /* * Add ContentType = Ms-SpcIndirectDataContext? * SEQ start -vv vv- Type ObjId * 1c70: 01 82 37 02 01 0c 31 02-30 00 30 19 06 09 2a 86 ..7...1.0.0...*. * 1c80: 48 86 f7 0d 01 09 03 31-0c 06 0a 2b 06 01 04 01 H......1...+.... * 1c90: 82 37 02 01 04 ^^- ^^- ObjId * ^- Set Of */ rcExit = SignToolPkcs7_AuthAttribsAddContentType(pAuthAttribs, pszContentTypeId); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; /* * Add Ms-SpcStatementType = Ms-SpcIndividualCodeSigning. * SEQ start -vv vv- Type ObjId * 1c90: 82 37 02 01 04 30 1c 06-0a 2b 06 01 04 01 82 37 .7...0...+.....7 * 1ca0: 02 01 0b 31 0e 30 0c 06-0a 2b 06 01 04 01 82 37 ...1.0...+.....7 * 1cb0: 02 01 15 ^^ ^^ ^^- ObjId * Set Of -^^ ^^- Sequence Of */ rcExit = SignToolPkcs7_AuthAttribsAddMsStatementType(pAuthAttribs, RTCRSPC_STMT_TYPE_INDIVIDUAL_CODE_SIGNING); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; /* * Add signing time. We add this, even if signtool.exe, since OpenSSL will always do it otherwise. */ if (!fNoSigningTime) /** @todo requires disabling the code in do_pkcs7_signed_attrib that adds it when absent */ { rcExit = SignToolPkcs7_AuthAttribsAddSigningTime(pAuthAttribs, SigningTime); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; } /** @todo more? Some certificate stuff? */ return RTEXITCODE_SUCCESS; } static RTEXITCODE SignToolPkcs7_AppendCounterSignature(PRTCRPKCS7SIGNERINFO pSignerInfo, PCRTCRPKCS7SIGNERINFO pCounterSignerInfo, unsigned cVerbosity) { /* Make sure the UnauthenticatedAttributes member is there. */ RTEXITCODE rcExit = SignToolPkcs7_EnsureUnauthenticatedAttributesPresent(pSignerInfo); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; #if 0 /* Windows won't accept multiple timestamps either way. Doing the latter as it makes more sense to me... */ /* Append an entry to UnauthenticatedAttributes. */ uint32_t iPos; int rc = RTCrPkcs7Attributes_InsertEx(&pSignerInfo->UnauthenticatedAttributes, 0 /*iPosition*/, NULL /*pToClone*/, &g_RTAsn1DefaultAllocator, &iPos); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrPkcs7Attributes_Append failed: %Rrc", rc); Assert(iPos < pSignerInfo->UnauthenticatedAttributes.cItems); Assert(iPos == 0); PRTCRPKCS7ATTRIBUTE pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iPos]; if (cVerbosity >= 2) RTMsgInfo("Adding UnauthenticatedAttribute #%u...", iPos); #else /* Look up the counter signature attribute, create one if needed. */ int rc; uint32_t iPos = 0; PRTCRPKCS7ATTRIBUTE pAttr = NULL; for (; iPos < pSignerInfo->UnauthenticatedAttributes.cItems; iPos++) { pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iPos]; if (pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES) break; } if (iPos >= pSignerInfo->UnauthenticatedAttributes.cItems) { /* Append a new entry to UnauthenticatedAttributes. */ rc = RTCrPkcs7Attributes_InsertEx(&pSignerInfo->UnauthenticatedAttributes, 0 /*iPosition*/, NULL /*pToClone*/, &g_RTAsn1DefaultAllocator, &iPos); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrPkcs7Attributes_Append failed: %Rrc", rc); Assert(iPos < pSignerInfo->UnauthenticatedAttributes.cItems); Assert(iPos == 0); pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iPos]; /* Create the attrib and its sub-set of counter signatures. */ rc = RTCrPkcs7Attribute_SetCounterSignatures(pAttr, NULL, pAttr->Allocation.pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrPkcs7Attribute_SetCounterSignatures failed: %Rrc", rc); } if (cVerbosity >= 2) RTMsgInfo("Adding UnauthenticatedAttribute #%u.%u...", iPos, pAttr->uValues.pCounterSignatures->cItems); #endif /* Insert the counter signature. */ rc = RTCrPkcs7SignerInfos_InsertEx(pAttr->uValues.pCounterSignatures, pAttr->uValues.pCounterSignatures->cItems /*iPosition*/, pCounterSignerInfo, pAttr->Allocation.pAllocator, NULL); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrPkcs7SignerInfos_InsertEx failed: %Rrc", rc); return RTEXITCODE_SUCCESS; } static RTEXITCODE SignToolPkcs7_AppendCertificate(PRTCRPKCS7SIGNEDDATA pSignedData, PCRTCRX509CERTIFICATE pCertToAppend) { if (pSignedData->Certificates.cItems == 0 && !RTCrPkcs7SetOfCerts_IsPresent(&pSignedData->Certificates)) return RTMsgErrorExitFailure("PKCS#7 signature includes no certificates! Didn't expect that"); /* Already there? */ PCRTCRX509CERTIFICATE pExisting = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(&pSignedData->Certificates, &pCertToAppend->TbsCertificate.Issuer, &pCertToAppend->TbsCertificate.SerialNumber); if (!pExisting || RTCrX509Certificate_Compare(pExisting, pCertToAppend) != 0) { /* Prepend a RTCRPKCS7CERT entry. */ uint32_t iPos; int rc = RTCrPkcs7SetOfCerts_InsertEx(&pSignedData->Certificates, 0 /*iPosition*/, NULL /*pToClone*/, &g_RTAsn1DefaultAllocator, &iPos); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrPkcs7SetOfCerts_Append failed: %Rrc", rc); PRTCRPKCS7CERT pCertEntry = pSignedData->Certificates.papItems[iPos]; /* Set (clone) the certificate. */ rc = RTCrPkcs7Cert_SetX509Cert(pCertEntry, pCertToAppend, pCertEntry->Allocation.pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrPkcs7Cert_X509Cert failed: %Rrc", rc); } return RTEXITCODE_SUCCESS; } #ifdef RT_OS_WINDOWS static PCRTUTF16 GetBCryptNameFromCrDigest(RTCRDIGEST hDigest) { switch (RTCrDigestGetType(hDigest)) { case RTDIGESTTYPE_MD2: return BCRYPT_MD2_ALGORITHM; case RTDIGESTTYPE_MD4: return BCRYPT_MD4_ALGORITHM; case RTDIGESTTYPE_SHA1: return BCRYPT_SHA1_ALGORITHM; case RTDIGESTTYPE_SHA256: return BCRYPT_SHA256_ALGORITHM; case RTDIGESTTYPE_SHA384: return BCRYPT_SHA384_ALGORITHM; case RTDIGESTTYPE_SHA512: return BCRYPT_SHA512_ALGORITHM; default: RTMsgError("No BCrypt translation for %s/%d!", RTCrDigestGetAlgorithmOid(hDigest), RTCrDigestGetType(hDigest)); return L"No BCrypt translation"; } } static RTEXITCODE SignToolPkcs7_Pkcs7SignStuffAgainWithReal(const char *pszWhat, SignToolKeyPair *pCertKeyPair, unsigned cVerbosity, PRTCRPKCS7CONTENTINFO pContentInfo, void **ppvSigned, size_t *pcbSigned) { RT_NOREF(cVerbosity); /* * First remove the fake certificate from the PKCS7 structure and insert the real one. */ PRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData; unsigned iCert = pSignedData->Certificates.cItems; unsigned cErased = 0; while (iCert-- > 0) { PCRTCRPKCS7CERT pCert = pSignedData->Certificates.papItems[iCert]; if ( pCert->enmChoice == RTCRPKCS7CERTCHOICE_X509 && RTCrX509Certificate_MatchIssuerAndSerialNumber(pCert->u.pX509Cert, &pCertKeyPair->pCertificate->TbsCertificate.Issuer, &pCertKeyPair->pCertificate->TbsCertificate.SerialNumber)) { RTCrPkcs7SetOfCerts_Erase(&pSignedData->Certificates, iCert); cErased++; } } if (cErased == 0) return RTMsgErrorExitFailure("(%s) Failed to find temporary signing certificate in PKCS#7 from OpenSSL: %u certs", pszWhat, pSignedData->Certificates.cItems); /* Then insert the real signing certificate. */ PCRTCRX509CERTIFICATE const pRealCertificate = pCertKeyPair->getRealCertificate(); RTEXITCODE rcExit = SignToolPkcs7_AppendCertificate(pSignedData, pRealCertificate); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; /* * Modify the signer info to reflect the real certificate. */ PRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[0]; RTCrX509Name_Delete(&pSignerInfo->IssuerAndSerialNumber.Name); int rc = RTCrX509Name_Clone(&pSignerInfo->IssuerAndSerialNumber.Name, &pRealCertificate->TbsCertificate.Issuer, &g_RTAsn1DefaultAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("(%s) RTCrX509Name_Clone failed: %Rrc", pszWhat, rc); RTAsn1Integer_Delete(&pSignerInfo->IssuerAndSerialNumber.SerialNumber); rc = RTAsn1Integer_Clone(&pSignerInfo->IssuerAndSerialNumber.SerialNumber, &pRealCertificate->TbsCertificate.SerialNumber, &g_RTAsn1DefaultAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("(%s) RTAsn1Integer_Clone failed: %Rrc", pszWhat, rc); /* There shouldn't be anything in the authenticated attributes that we need to modify... */ /* * Now a create a new signature using the real key. Since we haven't modified * the authenticated attributes, we can just hash them as-is. */ /* Create the hash to sign. */ RTCRDIGEST hDigest; rc = RTCrDigestCreateByObjId(&hDigest, &pSignerInfo->DigestAlgorithm.Algorithm); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("(%s) RTCrDigestCreateByObjId failed on '%s': %Rrc", pszWhat, pSignerInfo->DigestAlgorithm.Algorithm.szObjId, rc); rcExit = RTEXITCODE_FAILURE; RTERRINFOSTATIC ErrInfo; rc = RTCrPkcs7Attributes_HashAttributes(&pSignerInfo->AuthenticatedAttributes, hDigest, RTErrInfoInitStatic(&ErrInfo)); if (RT_SUCCESS(rc)) { BCRYPT_PKCS1_PADDING_INFO PaddingInfo = { GetBCryptNameFromCrDigest(hDigest) }; DWORD cbSignature = 0; SECURITY_STATUS rcNCrypt = NCryptSignHash(pCertKeyPair->hNCryptPrivateKey, &PaddingInfo, (PBYTE)RTCrDigestGetHash(hDigest), RTCrDigestGetHashSize(hDigest), NULL, 0, &cbSignature, NCRYPT_SILENT_FLAG | BCRYPT_PAD_PKCS1); if (rcNCrypt == ERROR_SUCCESS) { if (cVerbosity) RTMsgInfo("PaddingInfo: '%ls' cb=%#x, was %#zx\n", PaddingInfo.pszAlgId, cbSignature, pSignerInfo->EncryptedDigest.Asn1Core.cb); rc = RTAsn1OctetString_AllocContent(&pSignerInfo->EncryptedDigest, NULL /*pvSrc*/, cbSignature, &g_RTAsn1DefaultAllocator); if (RT_SUCCESS(rc)) { Assert(pSignerInfo->EncryptedDigest.Asn1Core.uData.pv); rcNCrypt = NCryptSignHash(pCertKeyPair->hNCryptPrivateKey, &PaddingInfo, (PBYTE)RTCrDigestGetHash(hDigest), RTCrDigestGetHashSize(hDigest), (PBYTE)pSignerInfo->EncryptedDigest.Asn1Core.uData.pv, cbSignature, &cbSignature, /*NCRYPT_SILENT_FLAG |*/ BCRYPT_PAD_PKCS1); if (rcNCrypt == ERROR_SUCCESS) { /* * Now we need to re-encode the whole thing and decode it again. */ PRTASN1CORE pRoot = RTCrPkcs7ContentInfo_GetAsn1Core(pContentInfo); uint32_t cbRealSigned; rc = RTAsn1EncodePrepare(pRoot, RTASN1ENCODE_F_DER, &cbRealSigned, RTErrInfoInitStatic(&ErrInfo)); if (RT_SUCCESS(rc)) { void *pvRealSigned = RTMemAllocZ(cbRealSigned); if (pvRealSigned) { rc = RTAsn1EncodeToBuffer(pRoot, RTASN1ENCODE_F_DER, pvRealSigned, cbRealSigned, RTErrInfoInitStatic(&ErrInfo)); if (RT_SUCCESS(rc)) { /* Decode it */ RTCrPkcs7ContentInfo_Delete(pContentInfo); RTASN1CURSORPRIMARY PrimaryCursor; RTAsn1CursorInitPrimary(&PrimaryCursor, pvRealSigned, cbRealSigned, RTErrInfoInitStatic(&ErrInfo), &g_RTAsn1DefaultAllocator, 0, pszWhat); rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, pContentInfo, "CI"); if (RT_SUCCESS(rc)) { Assert(RTCrPkcs7ContentInfo_IsSignedData(pContentInfo)); /* Almost done! Just replace output buffer. */ RTMemFree(*ppvSigned); *ppvSigned = pvRealSigned; *pcbSigned = cbRealSigned; pvRealSigned = NULL; rcExit = RTEXITCODE_SUCCESS; } else RTMsgError("(%s) RTCrPkcs7ContentInfo_DecodeAsn1 failed: %Rrc%#RTeim", pszWhat, rc, &ErrInfo.Core); } else RTMsgError("(%s) RTAsn1EncodeToBuffer failed: %Rrc%#RTeim", pszWhat, rc, &ErrInfo.Core); RTMemFree(pvRealSigned); } else RTMsgError("(%s) Failed to allocate %u bytes!", pszWhat, cbRealSigned); } else RTMsgError("(%s) RTAsn1EncodePrepare failed: %Rrc%#RTeim", pszWhat, rc, &ErrInfo.Core); } else RTMsgError("(%s) NCryptSignHash/2 failed: %Rwc %#x (%u)", pszWhat, rcNCrypt, rcNCrypt, rcNCrypt); } else RTMsgError("(%s) RTAsn1OctetString_AllocContent(,,%#x) failed: %Rrc", pszWhat, cbSignature, rc); } else RTMsgError("(%s) NCryptSignHash/1 failed: %Rwc %#x (%u)", pszWhat, rcNCrypt, rcNCrypt, rcNCrypt); } else RTMsgError("(%s) RTCrPkcs7Attributes_HashAttributes failed: %Rrc%#RTeim", pszWhat, rc, &ErrInfo.Core); RTCrDigestRelease(hDigest); return rcExit; } #endif /* RT_OS_WINDOWS */ static RTEXITCODE SignToolPkcs7_Pkcs7SignStuffInner(const char *pszWhat, const void *pvToDataToSign, size_t cbToDataToSign, PCRTCRPKCS7ATTRIBUTES pAuthAttribs, RTCRSTORE hAdditionalCerts, uint32_t fExtraFlags, RTDIGESTTYPE enmDigestType, SignToolKeyPair *pCertKeyPair, unsigned cVerbosity, void **ppvSigned, size_t *pcbSigned, PRTCRPKCS7CONTENTINFO pContentInfo, PRTCRPKCS7SIGNEDDATA *ppSignedData) { *ppvSigned = NULL; if (pcbSigned) *pcbSigned = 0; if (ppSignedData) *ppSignedData = NULL; /* Figure out how large the signature will be. */ uint32_t const fSignFlags = RTCRPKCS7SIGN_SD_F_USE_V1 | RTCRPKCS7SIGN_SD_F_NO_SMIME_CAP | fExtraFlags; size_t cbSigned = 1024; RTERRINFOSTATIC ErrInfo; int rc = RTCrPkcs7SimpleSignSignedData(fSignFlags, pCertKeyPair->pCertificate, pCertKeyPair->hPrivateKey, pvToDataToSign, cbToDataToSign,enmDigestType, hAdditionalCerts, pAuthAttribs, NULL, &cbSigned, RTErrInfoInitStatic(&ErrInfo)); if (rc != VERR_BUFFER_OVERFLOW) return RTMsgErrorExitFailure("(%s) RTCrPkcs7SimpleSignSignedData failed: %Rrc%#RTeim", pszWhat, rc, &ErrInfo.Core); /* Allocate memory for it and do the actual signing. */ void *pvSigned = RTMemAllocZ(cbSigned); if (!pvSigned) return RTMsgErrorExitFailure("(%s) Failed to allocate %#zx bytes for %s signature", pszWhat, cbSigned, pszWhat); rc = RTCrPkcs7SimpleSignSignedData(fSignFlags, pCertKeyPair->pCertificate, pCertKeyPair->hPrivateKey, pvToDataToSign, cbToDataToSign, enmDigestType, hAdditionalCerts, pAuthAttribs, pvSigned, &cbSigned, RTErrInfoInitStatic(&ErrInfo)); if (RT_SUCCESS(rc)) { if (cVerbosity > 2) RTMsgInfo("%s signature: %#zx bytes\n%.*Rhxd\n", pszWhat, cbSigned, cbSigned, pvSigned); /* * Decode the signature and check that it is SignedData. */ RTASN1CURSORPRIMARY PrimaryCursor; RTAsn1CursorInitPrimary(&PrimaryCursor, pvSigned, (uint32_t)cbSigned, RTErrInfoInitStatic(&ErrInfo), &g_RTAsn1DefaultAllocator, 0, pszWhat); rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, pContentInfo, "CI"); if (RT_SUCCESS(rc)) { if (RTCrPkcs7ContentInfo_IsSignedData(pContentInfo)) { #ifdef RT_OS_WINDOWS /* * If we're using a fake key+cert, we now have to re-do the signing using the real * key+cert and the windows crypto API. This kludge is necessary because we can't * typically get that the encoded private key, so it isn't possible to feed it to * openssl. */ RTEXITCODE rcExit = RTEXITCODE_SUCCESS; if (pCertKeyPair->pCertificateReal) rcExit = SignToolPkcs7_Pkcs7SignStuffAgainWithReal(pszWhat, pCertKeyPair, cVerbosity, pContentInfo, &pvSigned, &cbSigned); if (rcExit == RTEXITCODE_SUCCESS) #endif { /* * Set returns and maybe display the result before returning. */ *ppvSigned = pvSigned; if (pcbSigned) *pcbSigned = cbSigned; if (ppSignedData) *ppSignedData = pContentInfo->u.pSignedData; if (cVerbosity) { SHOWEXEPKCS7 ShowExe; RT_ZERO(ShowExe); ShowExe.cVerbosity = cVerbosity; HandleShowExeWorkerPkcs7Display(&ShowExe, pContentInfo->u.pSignedData, 0, pContentInfo); } return RTEXITCODE_SUCCESS; } } RTMsgError("(%s) RTCrPkcs7SimpleSignSignedData did not create SignedData: %s", pszWhat, pContentInfo->ContentType.szObjId); } else RTMsgError("(%s) RTCrPkcs7ContentInfo_DecodeAsn1 failed: %Rrc%#RTeim", pszWhat, rc, &ErrInfo.Core); RTCrPkcs7ContentInfo_Delete(pContentInfo); } RTMemFree(pvSigned); return RTEXITCODE_FAILURE; } static RTEXITCODE SignToolPkcs7_Pkcs7SignStuff(const char *pszWhat, const void *pvToDataToSign, size_t cbToDataToSign, PCRTCRPKCS7ATTRIBUTES pAuthAttribs, RTCRSTORE hAdditionalCerts, uint32_t fExtraFlags, RTDIGESTTYPE enmDigestType, SignToolKeyPair *pCertKeyPair, unsigned cVerbosity, void **ppvSigned, size_t *pcbSigned, PRTCRPKCS7CONTENTINFO pContentInfo, PRTCRPKCS7SIGNEDDATA *ppSignedData) { /* * Gather all additional certificates before doing the actual work. */ RTCRSTORE hAllAdditionalCerts = pCertKeyPair->assembleAllAdditionalCertificates(hAdditionalCerts); if (hAllAdditionalCerts == NIL_RTCRSTORE) return RTEXITCODE_FAILURE; RTEXITCODE rcExit = SignToolPkcs7_Pkcs7SignStuffInner(pszWhat, pvToDataToSign, cbToDataToSign, pAuthAttribs, hAllAdditionalCerts, fExtraFlags, enmDigestType, pCertKeyPair, cVerbosity, ppvSigned, pcbSigned, pContentInfo, ppSignedData); RTCrStoreRelease(hAllAdditionalCerts); return rcExit; } static RTEXITCODE SignToolPkcs7_AddTimestampSignatureEx(PRTCRPKCS7SIGNERINFO pSignerInfo, PRTCRPKCS7SIGNEDDATA pSignedData, unsigned cVerbosity, bool fReplaceExisting, RTTIMESPEC SigningTime, SignToolTimestampOpts *pTimestampOpts) { AssertReturn(!pTimestampOpts->isNewType(), RTMsgErrorExitFailure("New style signatures not supported yet")); /* * Create a set of attributes we need to include in the AuthenticatedAttributes * of the timestamp signature. */ RTCRPKCS7ATTRIBUTES AuthAttribs; int rc = RTCrPkcs7Attributes_Init(&AuthAttribs, &g_RTAsn1DefaultAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrPkcs7SetOfAttributes_Init failed: %Rrc", rc); RTEXITCODE rcExit = SignToolPkcs7_AddAuthAttribsForTimestamp(&AuthAttribs, pTimestampOpts->m_enmType, SigningTime, pTimestampOpts->getRealCertificate()); if (rcExit == RTEXITCODE_SUCCESS) { /* * Now create a PKCS#7 signature of the encrypted signature from the selected signer info. */ void *pvSigned = NULL; PRTCRPKCS7SIGNEDDATA pTsSignedData = NULL; RTCRPKCS7CONTENTINFO TsContentInfo; rcExit = SignToolPkcs7_Pkcs7SignStuffInner("timestamp", pSignerInfo->EncryptedDigest.Asn1Core.uData.pv, pSignerInfo->EncryptedDigest.Asn1Core.cb, &AuthAttribs, NIL_RTCRSTORE /*hAdditionalCerts*/, RTCRPKCS7SIGN_SD_F_DEATCHED, RTDIGESTTYPE_SHA1, pTimestampOpts, cVerbosity, &pvSigned, NULL /*pcbSigned*/, &TsContentInfo, &pTsSignedData); if (rcExit == RTEXITCODE_SUCCESS) { /* * If we're replacing existing timestamp signatures, remove old ones now. */ if ( fReplaceExisting && RTCrPkcs7Attributes_IsPresent(&pSignerInfo->UnauthenticatedAttributes)) { uint32_t iItem = pSignerInfo->UnauthenticatedAttributes.cItems; while (iItem-- > 0) { PRTCRPKCS7ATTRIBUTE pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iItem]; if (pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES) /* ASSUMES all counter sigs are timstamps */ { if (cVerbosity > 1) RTMsgInfo("Removing counter signature in attribute #%u\n", iItem); rc = RTCrPkcs7Attributes_Erase(&pSignerInfo->UnauthenticatedAttributes, iItem); if (RT_FAILURE(rc)) rcExit = RTMsgErrorExitFailure("RTCrPkcs7Attributes_Erase failed on #%u: %Rrc", iItem, rc); } } } /* * Add the new one. */ if (rcExit == RTEXITCODE_SUCCESS) rcExit = SignToolPkcs7_AppendCounterSignature(pSignerInfo, pTsSignedData->SignerInfos.papItems[0], cVerbosity); /* * Make sure the signing certificate is included. */ if (rcExit == RTEXITCODE_SUCCESS) { rcExit = SignToolPkcs7_AppendCertificate(pSignedData, pTimestampOpts->getRealCertificate()); PCRTCRCERTCTX pInterCaCtx = NULL; while ((pInterCaCtx = pTimestampOpts->findNextIntermediateCert(pInterCaCtx)) != NULL) if (rcExit == RTEXITCODE_SUCCESS) rcExit = SignToolPkcs7_AppendCertificate(pSignedData, pInterCaCtx->pCert); } /* * Clean up. */ RTCrPkcs7ContentInfo_Delete(&TsContentInfo); RTMemFree(pvSigned); } } RTCrPkcs7Attributes_Delete(&AuthAttribs); return rcExit; } static RTEXITCODE SignToolPkcs7_AddTimestampSignature(SIGNTOOLPKCS7EXE *pThis, unsigned cVerbosity, unsigned iSignature, bool fReplaceExisting, RTTIMESPEC SigningTime, SignToolTimestampOpts *pTimestampOpts) { /* * Locate the signature specified by iSignature and add a timestamp to it. */ PRTCRPKCS7SIGNEDDATA pSignedData = NULL; PRTCRPKCS7SIGNERINFO pSignerInfo = SignToolPkcs7_FindNestedSignatureByIndex(pThis, iSignature, &pSignedData); if (!pSignerInfo) return RTMsgErrorExitFailure("No signature #%u in %s", iSignature, pThis->pszFilename); return SignToolPkcs7_AddTimestampSignatureEx(pSignerInfo, pSignedData, cVerbosity, fReplaceExisting, SigningTime, pTimestampOpts); } typedef enum SIGNDATATWEAK { kSignDataTweak_NoTweak = 1, kSignDataTweak_RootIsParent } SIGNDATATWEAK; static RTEXITCODE SignToolPkcs7_SignData(SIGNTOOLPKCS7 *pThis, PRTASN1CORE pToSignRoot, SIGNDATATWEAK enmTweak, const char *pszContentTypeId, unsigned cVerbosity, uint32_t fExtraFlags, RTDIGESTTYPE enmSigType, bool fReplaceExisting, bool fNoSigningTime, SignToolKeyPair *pSigningCertKey, RTCRSTORE hAddCerts, RTTIMESPEC SigningTime, size_t cTimestampOpts, SignToolTimestampOpts *paTimestampOpts) { /* * Encode it. */ RTERRINFOSTATIC ErrInfo; uint32_t cbEncoded = 0; int rc = RTAsn1EncodePrepare(pToSignRoot, RTASN1ENCODE_F_DER, &cbEncoded, RTErrInfoInitStatic(&ErrInfo)); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTAsn1EncodePrepare failed: %Rrc%RTeim", rc, &ErrInfo.Core); if (cVerbosity >= 4) RTAsn1Dump(pToSignRoot, 0, 0, RTStrmDumpPrintfV, g_pStdOut); uint8_t *pbEncoded = (uint8_t *)RTMemTmpAllocZ(cbEncoded ); if (!pbEncoded) return RTMsgErrorExitFailure("Failed to allocate %#z bytes for encoding data we're signing (%s)", cbEncoded, pszContentTypeId); RTEXITCODE rcExit = RTEXITCODE_FAILURE; rc = RTAsn1EncodeToBuffer(pToSignRoot, RTASN1ENCODE_F_DER, pbEncoded, cbEncoded, RTErrInfoInitStatic(&ErrInfo)); if (RT_SUCCESS(rc)) { size_t const cbToSign = cbEncoded - (enmTweak == kSignDataTweak_RootIsParent ? pToSignRoot->cbHdr : 0); void const *pvToSign = pbEncoded + (enmTweak == kSignDataTweak_RootIsParent ? pToSignRoot->cbHdr : 0); /* * Create additional authenticated attributes. */ RTCRPKCS7ATTRIBUTES AuthAttribs; rc = RTCrPkcs7Attributes_Init(&AuthAttribs, &g_RTAsn1DefaultAllocator); if (RT_SUCCESS(rc)) { rcExit = SignToolPkcs7_AddAuthAttribsForImageOrCatSignature(&AuthAttribs, SigningTime, fNoSigningTime, pszContentTypeId); if (rcExit == RTEXITCODE_SUCCESS) { /* * Ditch the old signature if so desired. * (It is okay to do this in the CAT case too, as we've already * encoded the data and won't touch pToSignRoot any more.) */ pToSignRoot = NULL; /* (may become invalid if replacing) */ if (fReplaceExisting && pThis->pSignedData) { RTCrPkcs7ContentInfo_Delete(&pThis->ContentInfo); pThis->pSignedData = NULL; RTMemFree(pThis->pbBuf); pThis->pbBuf = NULL; pThis->cbBuf = 0; } /* * Do the actual signing. */ SIGNTOOLPKCS7 Src = { RTSIGNTOOLFILETYPE_DETECT, NULL, 0, NULL }; PSIGNTOOLPKCS7 pSigDst = !pThis->pSignedData ? pThis : &Src; rcExit = SignToolPkcs7_Pkcs7SignStuff("image", pvToSign, cbToSign, &AuthAttribs, hAddCerts, fExtraFlags | RTCRPKCS7SIGN_SD_F_NO_DATA_ENCAP, enmSigType /** @todo ?? */, pSigningCertKey, cVerbosity, (void **)&pSigDst->pbBuf, &pSigDst->cbBuf, &pSigDst->ContentInfo, &pSigDst->pSignedData); if (rcExit == RTEXITCODE_SUCCESS) { /* * Add the requested timestamp signatures if requested. */ for (size_t i = 0; rcExit == RTEXITCODE_SUCCESS &&i < cTimestampOpts; i++) if (paTimestampOpts[i].isComplete()) rcExit = SignToolPkcs7_AddTimestampSignatureEx(pSigDst->pSignedData->SignerInfos.papItems[0], pSigDst->pSignedData, cVerbosity, false /*fReplaceExisting*/, SigningTime, &paTimestampOpts[i]); /* * Append the signature to the existing one, if that's what we're doing. */ if (rcExit == RTEXITCODE_SUCCESS && pSigDst == &Src) rcExit = SignToolPkcs7_AddNestedSignature(pThis, &Src, cVerbosity, true /*fPrepend*/); /** @todo prepend/append option */ /* cleanup */ if (pSigDst == &Src) SignToolPkcs7_Delete(&Src); } } RTCrPkcs7Attributes_Delete(&AuthAttribs); } else RTMsgError("RTCrPkcs7SetOfAttributes_Init failed: %Rrc", rc); } else RTMsgError("RTAsn1EncodeToBuffer failed: %Rrc", rc); RTMemTmpFree(pbEncoded); return rcExit; } static RTEXITCODE SignToolPkcs7_SpcCompleteWithoutPageHashes(RTCRSPCINDIRECTDATACONTENT *pSpcIndData) { PCRTASN1ALLOCATORVTABLE const pAllocator = &g_RTAsn1DefaultAllocator; PRTCRSPCPEIMAGEDATA const pPeImage = pSpcIndData->Data.uValue.pPeImage; Assert(pPeImage); /* * Set it to File with an empty name. * RTCRSPCPEIMAGEDATA::Flags -vv * RTCRSPCPEIMAGEDATA::SeqCore -vv T0 -vv vv- pT2/CtxTag2 * 0040: 04 01 82 37 02 01 0f 30-09 03 01 00 a0 04 a2 02 ...7...0........ * 0050: 80 00 30 21 30 09 06 05-2b 0e 03 02 1a 05 00 04 ..0!0...+....... * ^^- pUcs2 / empty string */ /* Create an empty BMP string. */ RTASN1STRING EmptyStr; int rc = RTAsn1BmpString_Init(&EmptyStr, pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTAsn1BmpString_Init/Ucs2 failed: %Rrc", rc); /* Create an SPC string and use the above empty string with the Ucs2 setter. */ RTEXITCODE rcExit = RTEXITCODE_FAILURE; RTCRSPCSTRING SpcString; rc = RTCrSpcString_Init(&SpcString, pAllocator); if (RT_SUCCESS(rc)) { rc = RTCrSpcString_SetUcs2(&SpcString, &EmptyStr, pAllocator); if (RT_SUCCESS(rc)) { /* Create a temporary SpcLink with the empty SpcString. */ RTCRSPCLINK SpcLink; rc = RTCrSpcLink_Init(&SpcLink, pAllocator); if (RT_SUCCESS(rc)) { /* Use the setter on the SpcLink object to copy the SpcString to it. */ rc = RTCrSpcLink_SetFile(&SpcLink, &SpcString, pAllocator); if (RT_SUCCESS(rc)) { /* Use the setter to copy SpcLink to the PeImage structure. */ rc = RTCrSpcPeImageData_SetFile(pPeImage, &SpcLink, pAllocator); if (RT_SUCCESS(rc)) rcExit = RTEXITCODE_SUCCESS; else RTMsgError("RTCrSpcPeImageData_SetFile failed: %Rrc", rc); } else RTMsgError("RTCrSpcLink_SetFile failed: %Rrc", rc); RTCrSpcLink_Delete(&SpcLink); } else RTMsgError("RTCrSpcLink_Init failed: %Rrc", rc); } else RTMsgError("RTCrSpcString_SetUcs2 failed: %Rrc", rc); RTCrSpcString_Delete(&SpcString); } else RTMsgError("RTCrSpcString_Init failed: %Rrc", rc); RTAsn1BmpString_Delete(&EmptyStr); return rcExit; } static RTEXITCODE SignToolPkcs7_SpcAddImagePageHashes(SIGNTOOLPKCS7EXE *pThis, RTCRSPCINDIRECTDATACONTENT *pSpcIndData, RTDIGESTTYPE enmSigType) { PCRTASN1ALLOCATORVTABLE const pAllocator = &g_RTAsn1DefaultAllocator; PRTCRSPCPEIMAGEDATA const pPeImage = pSpcIndData->Data.uValue.pPeImage; Assert(pPeImage); /* * The hashes are stored in the 'Moniker' attribute. */ /* Create a temporary SpcLink with a default moniker. */ RTCRSPCLINK SpcLink; int rc = RTCrSpcLink_Init(&SpcLink, pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrSpcLink_Init failed: %Rrc", rc); rc = RTCrSpcLink_SetMoniker(&SpcLink, NULL, pAllocator); if (RT_SUCCESS(rc)) { /* Use the setter to copy SpcLink to the PeImage structure. */ rc = RTCrSpcPeImageData_SetFile(pPeImage, &SpcLink, pAllocator); if (RT_FAILURE(rc)) RTMsgError("RTCrSpcLink_SetFile failed: %Rrc", rc); } else RTMsgError("RTCrSpcLink_SetMoniker failed: %Rrc", rc); RTCrSpcLink_Delete(&SpcLink); if (RT_FAILURE(rc)) return RTEXITCODE_FAILURE; /* * Now go to work on the moniker. It doesn't have any autogenerated * setters, so we must do stuff manually. */ PRTCRSPCSERIALIZEDOBJECT pMoniker = pPeImage->T0.File.u.pMoniker; RTUUID Uuid; rc = RTUuidFromStr(&Uuid, RTCRSPCSERIALIZEDOBJECT_UUID_STR); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTUuidFromStr failed: %Rrc", rc); rc = RTAsn1OctetString_AllocContent(&pMoniker->Uuid, &Uuid, sizeof(Uuid), pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTAsn1String_InitWithValue/UUID failed: %Rrc", rc); /* Create a new set of attributes and associate this with the SerializedData member. */ PRTCRSPCSERIALIZEDOBJECTATTRIBUTES pSpcAttribs; rc = RTAsn1MemAllocZ(&pMoniker->SerializedData.EncapsulatedAllocation, (void **)&pSpcAttribs, sizeof(*pSpcAttribs)); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTAsn1MemAllocZ/pSpcAttribs failed: %Rrc", rc); pMoniker->SerializedData.pEncapsulated = RTCrSpcSerializedObjectAttributes_GetAsn1Core(pSpcAttribs); pMoniker->enmType = RTCRSPCSERIALIZEDOBJECTTYPE_ATTRIBUTES; pMoniker->u.pData = pSpcAttribs; rc = RTCrSpcSerializedObjectAttributes_Init(pSpcAttribs, pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrSpcSerializedObjectAttributes_Init failed: %Rrc", rc); /* * Add a single attribute to the set that we'll use for page hashes. */ int32_t iPos = RTCrSpcSerializedObjectAttributes_Append(pSpcAttribs); if (iPos < 0) return RTMsgErrorExitFailure("RTCrSpcSerializedObjectAttributes_Append failed: %Rrc", iPos); PRTCRSPCSERIALIZEDOBJECTATTRIBUTE pSpcObjAttr = pSpcAttribs->papItems[iPos]; if (enmSigType == RTDIGESTTYPE_SHA1) rc = RTCrSpcSerializedObjectAttribute_SetV1Hashes(pSpcObjAttr, NULL, pAllocator); else if (enmSigType == RTDIGESTTYPE_SHA256) rc = RTCrSpcSerializedObjectAttribute_SetV2Hashes(pSpcObjAttr, NULL, pAllocator); else rc = VERR_CR_DIGEST_NOT_SUPPORTED; if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrSpcSerializedObjectAttribute_SetV1Hashes/SetV2Hashes failed: %Rrc", rc); PRTCRSPCSERIALIZEDPAGEHASHES pSpcPageHashes = pSpcObjAttr->u.pPageHashes; Assert(pSpcPageHashes); /* * Now ask the loader for the number of pages in the page hash table * and calculate its size. */ uint32_t cPages = 0; rc = RTLdrQueryPropEx(pThis->hLdrMod, RTLDRPROP_HASHABLE_PAGES, NULL, &cPages, sizeof(cPages), NULL); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTLdrQueryPropEx/RTLDRPROP_HASHABLE_PAGES failed: %Rrc", rc); uint32_t const cbHash = RTCrDigestTypeToHashSize(enmSigType); AssertReturn(cbHash > 0, RTMsgErrorExitFailure("Invalid value: enmSigType=%d", enmSigType)); uint32_t const cbTable = (sizeof(uint32_t) + cbHash) * cPages; /* * Allocate memory in the octect string. */ rc = RTAsn1ContentAllocZ(&pSpcPageHashes->RawData.Asn1Core, cbTable, pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTAsn1ContentAllocZ failed to allocate %#x bytes for page hashes: %Rrc", cbTable, rc); pSpcPageHashes->pData = (PCRTCRSPCPEIMAGEPAGEHASHES)pSpcPageHashes->RawData.Asn1Core.uData.pu8; RTLDRPROP enmLdrProp; switch (enmSigType) { case RTDIGESTTYPE_SHA1: enmLdrProp = RTLDRPROP_SHA1_PAGE_HASHES; break; case RTDIGESTTYPE_SHA256: enmLdrProp = RTLDRPROP_SHA256_PAGE_HASHES; break; default: AssertFailedReturn(RTMsgErrorExitFailure("Invalid value: enmSigType=%d", enmSigType)); } rc = RTLdrQueryPropEx(pThis->hLdrMod, enmLdrProp, NULL, (void *)pSpcPageHashes->RawData.Asn1Core.uData.pv, cbTable, NULL); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTLdrQueryPropEx/RTLDRPROP_SHA?_PAGE_HASHES/%#x failed: %Rrc", cbTable, rc); return RTEXITCODE_SUCCESS; } static RTEXITCODE SignToolPkcs7_SpcAddImageHash(SIGNTOOLPKCS7EXE *pThis, RTCRSPCINDIRECTDATACONTENT *pSpcIndData, RTDIGESTTYPE enmSigType) { uint32_t const cbHash = RTCrDigestTypeToHashSize(enmSigType); const char * const pszAlgId = RTCrDigestTypeToAlgorithmOid(enmSigType); /* * Ask the loader for the hash. */ uint8_t abHash[RTSHA512_HASH_SIZE]; int rc = RTLdrHashImage(pThis->hLdrMod, enmSigType, abHash, sizeof(abHash)); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTLdrHashImage/%s failed: %Rrc", RTCrDigestTypeToName(enmSigType), rc); /* * Set it. */ /** @todo no setter, this should be okay, though... */ rc = RTAsn1ObjId_InitFromString(&pSpcIndData->DigestInfo.DigestAlgorithm.Algorithm, pszAlgId, &g_RTAsn1DefaultAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTAsn1ObjId_InitFromString/%s failed: %Rrc", pszAlgId, rc); RTAsn1DynType_SetToNull(&pSpcIndData->DigestInfo.DigestAlgorithm.Parameters); /* ASSUMES RSA or similar */ rc = RTAsn1ContentDup(&pSpcIndData->DigestInfo.Digest.Asn1Core, abHash, cbHash, &g_RTAsn1DefaultAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTAsn1ContentDup/%#x failed: %Rrc", cbHash, rc); return RTEXITCODE_SUCCESS; } static RTEXITCODE SignToolPkcs7_AddOrReplaceSignature(SIGNTOOLPKCS7EXE *pThis, unsigned cVerbosity, RTDIGESTTYPE enmSigType, bool fReplaceExisting, bool fHashPages, bool fNoSigningTime, SignToolKeyPair *pSigningCertKey, RTCRSTORE hAddCerts, RTTIMESPEC SigningTime, size_t cTimestampOpts, SignToolTimestampOpts *paTimestampOpts) { /* * We must construct the data to be packed into the PKCS#7 signature * and signed. */ PCRTASN1ALLOCATORVTABLE const pAllocator = &g_RTAsn1DefaultAllocator; RTCRSPCINDIRECTDATACONTENT SpcIndData; int rc = RTCrSpcIndirectDataContent_Init(&SpcIndData, pAllocator); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrSpcIndirectDataContent_Init failed: %Rrc", rc); /* Set the data to PE image. */ /** @todo Generalize the Type + enmType DYN stuff and generate setters. */ Assert(SpcIndData.Data.enmType == RTCRSPCAAOVTYPE_NOT_PRESENT); Assert(SpcIndData.Data.uValue.pPeImage == NULL); RTEXITCODE rcExit; rc = RTAsn1ObjId_SetFromString(&SpcIndData.Data.Type, RTCRSPCPEIMAGEDATA_OID, pAllocator); if (RT_SUCCESS(rc)) { SpcIndData.Data.enmType = RTCRSPCAAOVTYPE_PE_IMAGE_DATA; rc = RTAsn1MemAllocZ(&SpcIndData.Data.Allocation, (void **)&SpcIndData.Data.uValue.pPeImage, sizeof(*SpcIndData.Data.uValue.pPeImage)); if (RT_SUCCESS(rc)) { rc = RTCrSpcPeImageData_Init(SpcIndData.Data.uValue.pPeImage, pAllocator); if (RT_SUCCESS(rc)) { /* Old (SHA1) signatures has a Flags member, it's zero bits, though. */ if (enmSigType == RTDIGESTTYPE_SHA1) { uint8_t bFlags = 0; RTASN1BITSTRING Flags; rc = RTAsn1BitString_InitWithData(&Flags, &bFlags, 0, pAllocator); if (RT_SUCCESS(rc)) { rc = RTCrSpcPeImageData_SetFlags(SpcIndData.Data.uValue.pPeImage, &Flags, pAllocator); RTAsn1BitString_Delete(&Flags); if (RT_FAILURE(rc)) rcExit = RTMsgErrorExitFailure("RTCrSpcPeImageData_SetFlags failed: %Rrc", rc); } else rcExit = RTMsgErrorExitFailure("RTAsn1BitString_InitWithData failed: %Rrc", rc); } /* * Add the hashes. */ rcExit = SignToolPkcs7_SpcAddImageHash(pThis, &SpcIndData, enmSigType); if (rcExit == RTEXITCODE_SUCCESS) { if (fHashPages) rcExit = SignToolPkcs7_SpcAddImagePageHashes(pThis, &SpcIndData, enmSigType); else rcExit = SignToolPkcs7_SpcCompleteWithoutPageHashes(&SpcIndData); /* * Encode and sign the SPC data, timestamp it, and line it up for adding to the executable. */ if (rcExit == RTEXITCODE_SUCCESS) rcExit = SignToolPkcs7_SignData(pThis, RTCrSpcIndirectDataContent_GetAsn1Core(&SpcIndData), kSignDataTweak_NoTweak, RTCRSPCINDIRECTDATACONTENT_OID, cVerbosity, 0, enmSigType, fReplaceExisting, fNoSigningTime, pSigningCertKey, hAddCerts, SigningTime, cTimestampOpts, paTimestampOpts); } } else rcExit = RTMsgErrorExitFailure("RTCrPkcs7SignerInfos_Init failed: %Rrc", rc); } else rcExit = RTMsgErrorExitFailure("RTAsn1MemAllocZ failed for RTCRSPCPEIMAGEDATA: %Rrc", rc); } else rcExit = RTMsgErrorExitFailure("RTAsn1ObjId_SetWithString/SpcPeImageData failed: %Rrc", rc); RTCrSpcIndirectDataContent_Delete(&SpcIndData); return rcExit; } static RTEXITCODE SignToolPkcs7_AddOrReplaceCatSignature(SIGNTOOLPKCS7 *pThis, unsigned cVerbosity, RTDIGESTTYPE enmSigType, bool fReplaceExisting, bool fNoSigningTime, SignToolKeyPair *pSigningCertKey, RTCRSTORE hAddCerts, RTTIMESPEC SigningTime, size_t cTimestampOpts, SignToolTimestampOpts *paTimestampOpts) { AssertReturn(pThis->pSignedData, RTMsgErrorExitFailure("pSignedData is NULL!")); /* * Figure out what to sign first. */ uint32_t fExtraFlags = 0; PRTASN1CORE pToSign = &pThis->pSignedData->ContentInfo.Content.Asn1Core; const char *pszType = pThis->pSignedData->ContentInfo.ContentType.szObjId; if (!fReplaceExisting && pThis->pSignedData->SignerInfos.cItems == 0) fReplaceExisting = true; if (!fReplaceExisting) { pszType = RTCR_PKCS7_DATA_OID; fExtraFlags |= RTCRPKCS7SIGN_SD_F_DEATCHED; } /* * Do the signing. */ RTEXITCODE rcExit = SignToolPkcs7_SignData(pThis, pToSign, kSignDataTweak_RootIsParent, pszType, cVerbosity, fExtraFlags, enmSigType, fReplaceExisting, fNoSigningTime, pSigningCertKey, hAddCerts, SigningTime, cTimestampOpts, paTimestampOpts); /* probably need to clean up stuff related to nested signatures here later... */ return rcExit; } #endif /* !IPRT_SIGNTOOL_NO_SIGNING */ /********************************************************************************************************************************* * Option handlers shared by 'sign-exe', 'sign-cat', 'add-timestamp-exe-signature' and others. * *********************************************************************************************************************************/ #ifndef IPRT_SIGNTOOL_NO_SIGNING static RTEXITCODE HandleOptAddCert(PRTCRSTORE phStore, const char *pszFile) { if (*phStore == NIL_RTCRSTORE) { int rc = RTCrStoreCreateInMem(phStore, 2); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrStoreCreateInMem(,2) failed: %Rrc", rc); } RTERRINFOSTATIC ErrInfo; int rc = RTCrStoreCertAddFromFile(*phStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND, pszFile, RTErrInfoInitStatic(&ErrInfo)); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("Error reading certificate from '%s': %Rrc%#RTeim", pszFile, rc, &ErrInfo.Core); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleOptSignatureType(RTDIGESTTYPE *penmSigType, const char *pszType) { if ( RTStrICmpAscii(pszType, "sha1") == 0 || RTStrICmpAscii(pszType, "sha-1") == 0) *penmSigType = RTDIGESTTYPE_SHA1; else if ( RTStrICmpAscii(pszType, "sha256") == 0 || RTStrICmpAscii(pszType, "sha-256") == 0) *penmSigType = RTDIGESTTYPE_SHA256; else return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown signature type: %s (expected sha1 or sha256)", pszType); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleOptTimestampType(SignToolTimestampOpts *pTimestampOpts, const char *pszType) { if (strcmp(pszType, "old") == 0) pTimestampOpts->m_enmType = kTimestampType_Old; else if (strcmp(pszType, "new") == 0) pTimestampOpts->m_enmType = kTimestampType_New; else return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown timestamp type: %s", pszType); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleOptTimestampOverride(PRTTIMESPEC pSigningTime, const char *pszPartialTs) { /* * First try use it as-is. */ if (RTTimeSpecFromString(pSigningTime, pszPartialTs) != NULL) return RTEXITCODE_SUCCESS; /* Check the input against a pattern, making sure we've got something that makes sense before trying to merge. */ size_t const cchPartialTs = strlen(pszPartialTs); static char s_szPattern[] = "0000-00-00T00:00:"; if (cchPartialTs > sizeof(s_szPattern) - 1) /* It is not a partial timestamp if we've got the seconds component. */ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid timestamp: %s", pszPartialTs); for (size_t off = 0; off < cchPartialTs; off++) switch (s_szPattern[off]) { case '0': if (!RT_C_IS_DIGIT(pszPartialTs[off])) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid timestamp, expected digit at position %u: %s", off + 1, pszPartialTs); break; case '-': case ':': if (pszPartialTs[off] != s_szPattern[off]) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid timestamp, expected '%c' at position %u: %s", s_szPattern[off], off + 1, pszPartialTs); break; case 'T': if ( pszPartialTs[off] != 'T' && pszPartialTs[off] != 't' && pszPartialTs[off] != ' ') return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid timestamp, expected 'T' or space at position %u: %s", off + 1, pszPartialTs); break; default: return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Internal error"); } if (RT_C_IS_DIGIT(s_szPattern[cchPartialTs]) && RT_C_IS_DIGIT(s_szPattern[cchPartialTs - 1])) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Incomplete timstamp component: %s", pszPartialTs); /* * Take the current time and merge in the components from pszPartialTs. */ char szSigningTime[RTTIME_STR_LEN]; RTTIMESPEC Now; RTTimeSpecToString(RTTimeNow(&Now), szSigningTime, sizeof(szSigningTime)); memcpy(szSigningTime, pszPartialTs, cchPartialTs); szSigningTime[4+1+2+1+2] = 'T'; /* Fix 29th for non-leap override: */ if (memcmp(&szSigningTime[5], RT_STR_TUPLE("02-29")) == 0) { if (!RTTimeIsLeapYear(RTStrToUInt32(szSigningTime))) szSigningTime[9] = '8'; } if (RTTimeSpecFromString(pSigningTime, szSigningTime) == NULL) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid timestamp: %s (%s)", pszPartialTs, szSigningTime); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleOptFileType(RTSIGNTOOLFILETYPE *penmFileType, const char *pszType) { if (strcmp(pszType, "detect") == 0 || strcmp(pszType, "auto") == 0) *penmFileType = RTSIGNTOOLFILETYPE_DETECT; else if (strcmp(pszType, "exe") == 0) *penmFileType = RTSIGNTOOLFILETYPE_EXE; else if (strcmp(pszType, "cat") == 0) *penmFileType = RTSIGNTOOLFILETYPE_CAT; else return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown forced file type: %s", pszType); return RTEXITCODE_SUCCESS; } #endif /* !IPRT_SIGNTOOL_NO_SIGNING */ /** * Detects the type of files @a pszFile is (by reading from it). * * @returns The file type, or RTSIGNTOOLFILETYPE_UNKNOWN (error displayed). * @param enmForceFileType Usually set to RTSIGNTOOLFILETYPE_DETECT, but if * not we'll return this without probing the file. * @param pszFile The name of the file to detect the type of. */ static RTSIGNTOOLFILETYPE DetectFileType(RTSIGNTOOLFILETYPE enmForceFileType, const char *pszFile) { /* * Forced? */ if (enmForceFileType != RTSIGNTOOLFILETYPE_DETECT) return enmForceFileType; /* * Read the start of the file. */ RTFILE hFile = NIL_RTFILE; int rc = RTFileOpen(&hFile, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); if (RT_FAILURE(rc)) { RTMsgError("Error opening '%s' for reading: %Rrc", pszFile, rc); return RTSIGNTOOLFILETYPE_UNKNOWN; } union { uint8_t ab[256]; uint16_t au16[256/2]; uint32_t au32[256/4]; } uBuf; RT_ZERO(uBuf); size_t cbRead = 0; rc = RTFileRead(hFile, &uBuf, sizeof(uBuf), &cbRead); if (RT_FAILURE(rc)) RTMsgError("Error reading from '%s': %Rrc", pszFile, rc); uint64_t cbFile; int rcSize = RTFileQuerySize(hFile, &cbFile); if (RT_FAILURE(rcSize)) RTMsgError("Error querying size of '%s': %Rrc", pszFile, rc); RTFileClose(hFile); if (RT_FAILURE(rc) || RT_FAILURE(rcSize)) return RTSIGNTOOLFILETYPE_UNKNOWN; /* * Try guess the kind of file. */ /* All the executable magics we know: */ if ( uBuf.au16[0] == RT_H2LE_U16_C(IMAGE_DOS_SIGNATURE) || uBuf.au16[0] == RT_H2LE_U16_C(IMAGE_NE_SIGNATURE) || uBuf.au16[0] == RT_H2LE_U16_C(IMAGE_LX_SIGNATURE) || uBuf.au16[0] == RT_H2LE_U16_C(IMAGE_LE_SIGNATURE) || uBuf.au32[0] == RT_H2LE_U32_C(IMAGE_NT_SIGNATURE) || uBuf.au32[0] == RT_H2LE_U32_C(IMAGE_ELF_SIGNATURE) || uBuf.au32[0] == IMAGE_FAT_SIGNATURE || uBuf.au32[0] == IMAGE_FAT_SIGNATURE_OE || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE || uBuf.au32[0] == IMAGE_MACHO32_SIGNATURE_OE || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE || uBuf.au32[0] == IMAGE_MACHO64_SIGNATURE_OE) return RTSIGNTOOLFILETYPE_EXE; /* * Catalog files are PKCS#7 SignedData and starts with a ContentInfo, i.e.: * SEQUENCE { * contentType OBJECT IDENTIFIER, * content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL * } * * We ASSUME that it's DER encoded and doesn't use an indefinite length form * at the start and that contentType is signedData (1.2.840.113549.1.7.2). * * Example of a 10353 (0x2871) byte long file: * vv-------- contentType -------vv * 00000000 30 82 28 6D 06 09 2A 86 48 86 F7 0D 01 07 02 A0 * 00000010 82 28 5E 30 82 28 5A 02 01 01 31 0B 30 09 06 05 */ if ( uBuf.ab[0] == (ASN1_TAG_SEQUENCE | ASN1_TAGFLAG_CONSTRUCTED) && uBuf.ab[1] != 0x80 /* not indefinite form */ && uBuf.ab[1] > 0x30) { size_t off = 1; uint32_t cbRec = uBuf.ab[1]; if (cbRec & 0x80) { cbRec &= 0x7f; off += cbRec; switch (cbRec) { case 1: cbRec = uBuf.ab[2]; break; case 2: cbRec = RT_MAKE_U16( uBuf.ab[3], uBuf.ab[2]); break; case 3: cbRec = RT_MAKE_U32_FROM_U8(uBuf.ab[4], uBuf.ab[3], uBuf.ab[2], 0); break; case 4: cbRec = RT_MAKE_U32_FROM_U8(uBuf.ab[5], uBuf.ab[4], uBuf.ab[3], uBuf.ab[2]); break; default: cbRec = UINT32_MAX; break; } } if (off <= 5) { off++; if (off + cbRec == cbFile) { /* If the contentType is signedData we're going to treat it as a catalog file, we don't currently much care about the signed content of a cat file. */ static const uint8_t s_abSignedDataOid[] = { ASN1_TAG_OID, 9 /*length*/, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02 }; if (memcmp(&uBuf.ab[off], s_abSignedDataOid, sizeof(s_abSignedDataOid)) == 0) return RTSIGNTOOLFILETYPE_CAT; } } } RTMsgError("Unable to detect type of '%s'", pszFile); return RTSIGNTOOLFILETYPE_UNKNOWN; } /********************************************************************************************************************************* * The 'extract-exe-signer-cert' command. * *********************************************************************************************************************************/ static RTEXITCODE HelpExtractExeSignerCert(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "extract-exe-signer-cert [--ber|--cer|--der] [--signature-index|-i ] [--input|--exe|-e] [--output|-o] \n"); return RTEXITCODE_SUCCESS; } static RTEXITCODE WriteCertToFile(PCRTCRX509CERTIFICATE pCert, const char *pszFilename, bool fForce) { RTEXITCODE rcExit = RTEXITCODE_FAILURE; RTFILE hFile; int rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | (fForce ? RTFILE_O_CREATE_REPLACE : RTFILE_O_CREATE)); if (RT_SUCCESS(rc)) { uint32_t cbCert = pCert->SeqCore.Asn1Core.cbHdr + pCert->SeqCore.Asn1Core.cb; rc = RTFileWrite(hFile, pCert->SeqCore.Asn1Core.uData.pu8 - pCert->SeqCore.Asn1Core.cbHdr, cbCert, NULL); if (RT_SUCCESS(rc)) { rc = RTFileClose(hFile); if (RT_SUCCESS(rc)) { hFile = NIL_RTFILE; rcExit = RTEXITCODE_SUCCESS; RTMsgInfo("Successfully wrote %u bytes to '%s'", cbCert, pszFilename); } else RTMsgError("RTFileClose failed: %Rrc", rc); } else RTMsgError("RTFileWrite failed: %Rrc", rc); RTFileClose(hFile); } else RTMsgError("Error opening '%s' for writing: %Rrc", pszFilename, rc); return rcExit; } static RTEXITCODE HandleExtractExeSignerCert(int cArgs, char **papszArgs) { /* * Parse arguments. */ static const RTGETOPTDEF s_aOptions[] = { { "--ber", 'b', RTGETOPT_REQ_NOTHING }, { "--cer", 'c', RTGETOPT_REQ_NOTHING }, { "--der", 'd', RTGETOPT_REQ_NOTHING }, { "--exe", 'e', RTGETOPT_REQ_STRING }, { "--input", 'e', RTGETOPT_REQ_STRING }, { "--output", 'o', RTGETOPT_REQ_STRING }, { "--signature-index", 'i', RTGETOPT_REQ_UINT32 }, { "--force", 'f', RTGETOPT_REQ_NOTHING }, }; const char *pszExe = NULL; const char *pszOut = NULL; RTLDRARCH enmLdrArch = RTLDRARCH_WHATEVER; unsigned cVerbosity = 0; uint32_t fCursorFlags = RTASN1CURSOR_FLAGS_DER; uint32_t iSignature = 0; bool fForce = false; RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); AssertRCReturn(rc, RTEXITCODE_FAILURE); RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion))) { switch (ch) { case 'e': pszExe = ValueUnion.psz; break; case 'o': pszOut = ValueUnion.psz; break; case 'b': fCursorFlags = 0; break; case 'c': fCursorFlags = RTASN1CURSOR_FLAGS_CER; break; case 'd': fCursorFlags = RTASN1CURSOR_FLAGS_DER; break; case 'f': fForce = true; break; case 'i': iSignature = ValueUnion.u32; break; case 'V': return HandleVersion(cArgs, papszArgs); case 'h': return HelpExtractExeSignerCert(g_pStdOut, RTSIGNTOOLHELP_FULL); case VINF_GETOPT_NOT_OPTION: if (!pszExe) pszExe = ValueUnion.psz; else if (!pszOut) pszOut = ValueUnion.psz; else return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many file arguments: %s", ValueUnion.psz); break; default: return RTGetOptPrintError(ch, &ValueUnion); } } if (!pszExe) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given."); if (!pszOut) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No output file given."); if (!fForce && RTPathExists(pszOut)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "The output file '%s' exists.", pszOut); /* * Do it. */ /* Read & decode the PKCS#7 signature. */ SIGNTOOLPKCS7EXE This; RTEXITCODE rcExit = SignToolPkcs7Exe_InitFromFile(&This, pszExe, cVerbosity, enmLdrArch); if (rcExit == RTEXITCODE_SUCCESS) { /* Find the signing certificate (ASSUMING that the certificate used is shipped in the set of certificates). */ PRTCRPKCS7SIGNEDDATA pSignedData; PCRTCRPKCS7SIGNERINFO pSignerInfo = SignToolPkcs7_FindNestedSignatureByIndex(&This, iSignature, &pSignedData); rcExit = RTEXITCODE_FAILURE; if (pSignerInfo) { PCRTCRPKCS7ISSUERANDSERIALNUMBER pISN = &pSignedData->SignerInfos.papItems[0]->IssuerAndSerialNumber; PCRTCRX509CERTIFICATE pCert; pCert = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(&pSignedData->Certificates, &pISN->Name, &pISN->SerialNumber); if (pCert) { /* * Write it out. */ rcExit = WriteCertToFile(pCert, pszOut, fForce); } else RTMsgError("Certificate not found."); } else RTMsgError("Could not locate signature #%u!", iSignature); /* Delete the signature data. */ SignToolPkcs7Exe_Delete(&This); } return rcExit; } /********************************************************************************************************************************* * The 'extract-signer-root' & 'extract-timestamp-root' commands. * *********************************************************************************************************************************/ class BaseExtractState { public: const char *pszFile; const char *pszOut; RTLDRARCH enmLdrArch; unsigned cVerbosity; uint32_t iSignature; bool fForce; /** Timestamp or main signature. */ bool const fTimestamp; BaseExtractState(bool a_fTimestamp) : pszFile(NULL) , pszOut(NULL) , enmLdrArch(RTLDRARCH_WHATEVER) , cVerbosity(0) , iSignature(0) , fForce(false) , fTimestamp(a_fTimestamp) { } }; class RootExtractState : public BaseExtractState { public: CryptoStore RootStore; CryptoStore AdditionalStore; RootExtractState(bool a_fTimestamp) : BaseExtractState(a_fTimestamp) , RootStore() , AdditionalStore() { } /** * Creates the two stores, filling the root one with trusted CAs and * certificates found on the system or in the user's account. */ bool init(void) { int rc = RTCrStoreCreateInMem(&this->RootStore.m_hStore, 0); if (RT_SUCCESS(rc)) { rc = RTCrStoreCreateInMem(&this->AdditionalStore.m_hStore, 0); if (RT_SUCCESS(rc)) return true; } RTMsgError("RTCrStoreCreateInMem failed: %Rrc", rc); return false; } }; /** * Locates the target signature and certificate collection. */ static PRTCRPKCS7SIGNERINFO BaseExtractFindSignerInfo(SIGNTOOLPKCS7 *pThis, BaseExtractState *pState, PRTCRPKCS7SIGNEDDATA *ppSignedData, PCRTCRPKCS7SETOFCERTS *ppCerts) { *ppSignedData = NULL; *ppCerts = NULL; /* * Locate the target signature. */ PRTCRPKCS7SIGNEDDATA pSignedData = NULL; PRTCRPKCS7SIGNERINFO pSignerInfo = SignToolPkcs7_FindNestedSignatureByIndex(pThis, pState->iSignature, &pSignedData); if (pSignerInfo) { /* * If the target is the timestamp we have to locate the relevant * timestamp signature and adjust the return values. */ if (pState->fTimestamp) { for (uint32_t iItem = 0; iItem < pSignerInfo->UnauthenticatedAttributes.cItems; iItem++) { PCRTCRPKCS7ATTRIBUTE pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[iItem]; if (pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES) { /* ASSUME that all counter signatures are timestamping. */ if (pAttr->uValues.pCounterSignatures->cItems > 0) { *ppSignedData = pSignedData; *ppCerts = &pSignedData->Certificates; return pAttr->uValues.pCounterSignatures->papItems[0]; } RTMsgWarning("Timestamp signature attribute is empty!"); } else if (pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_TIMESTAMP) { /* ASSUME that all valid timestamp signatures for now, pick the first. */ if (pAttr->uValues.pContentInfos->cItems > 0) { PCRTCRPKCS7CONTENTINFO pContentInfo = pAttr->uValues.pContentInfos->papItems[0]; if (RTAsn1ObjId_CompareWithString(&pContentInfo->ContentType, RTCR_PKCS7_SIGNED_DATA_OID) == 0) { pSignedData = pContentInfo->u.pSignedData; if (RTAsn1ObjId_CompareWithString(&pSignedData->ContentInfo.ContentType, RTCRTSPTSTINFO_OID) == 0) { if (pSignedData->SignerInfos.cItems > 0) { *ppSignedData = pSignedData; *ppCerts = &pSignedData->Certificates; return pSignedData->SignerInfos.papItems[0]; } RTMsgWarning("Timestamp signature has no signers!"); } else RTMsgWarning("Timestamp signature contains wrong content (%s)!", pSignedData->ContentInfo.ContentType.szObjId); } else RTMsgWarning("Timestamp signature is not SignedData but %s!", pContentInfo->ContentType.szObjId); } else RTMsgWarning("Timestamp signature attribute is empty!"); } } RTMsgError("Cound not find a timestamp signature associated with signature #%u!", pState->iSignature); pSignerInfo = NULL; } else { *ppSignedData = pSignedData; *ppCerts = &pSignedData->Certificates; } } else RTMsgError("Could not locate signature #%u!", pState->iSignature); return pSignerInfo; } /** @callback_method_impl{FNRTDUMPPRINTFV} */ static DECLCALLBACK(void) DumpToStdOutPrintfV(void *pvUser, const char *pszFormat, va_list va) { RT_NOREF(pvUser); RTPrintfV(pszFormat, va); } static RTEXITCODE RootExtractWorker2(SIGNTOOLPKCS7 *pThis, RootExtractState *pState, PRTERRINFOSTATIC pStaticErrInfo) { /* * Locate the target signature. */ PRTCRPKCS7SIGNEDDATA pSignedData; PCRTCRPKCS7SETOFCERTS pCerts; PCRTCRPKCS7SIGNERINFO pSignerInfo = BaseExtractFindSignerInfo(pThis,pState, &pSignedData, &pCerts); if (!pSignerInfo) return RTMsgErrorExitFailure("Could not locate signature #%u!", pState->iSignature); /* The next bit is modelled on first half of rtCrPkcs7VerifySignerInfo. */ /* * Locate the signing certificate. */ PCRTCRCERTCTX pSignerCertCtx = RTCrStoreCertByIssuerAndSerialNo(pState->RootStore.m_hStore, &pSignerInfo->IssuerAndSerialNumber.Name, &pSignerInfo->IssuerAndSerialNumber.SerialNumber); if (!pSignerCertCtx) pSignerCertCtx = RTCrStoreCertByIssuerAndSerialNo(pState->AdditionalStore.m_hStore, &pSignerInfo->IssuerAndSerialNumber.Name, &pSignerInfo->IssuerAndSerialNumber.SerialNumber); PCRTCRX509CERTIFICATE pSignerCert; if (pSignerCertCtx) pSignerCert = pSignerCertCtx->pCert; else { pSignerCert = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(pCerts, &pSignerInfo->IssuerAndSerialNumber.Name, &pSignerInfo->IssuerAndSerialNumber.SerialNumber); if (!pSignerCert) return RTMsgErrorExitFailure("Certificate not found: serial=%.*Rhxs", pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.cb, pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.uData.pv); } /* * Now we build paths so we can get to the root certificate. */ RTCRX509CERTPATHS hCertPaths; int rc = RTCrX509CertPathsCreate(&hCertPaths, pSignerCert); if (RT_FAILURE(rc)) return RTMsgErrorExitFailure("RTCrX509CertPathsCreate failed: %Rrc", rc); /* Configure: */ RTEXITCODE rcExit = RTEXITCODE_FAILURE; rc = RTCrX509CertPathsSetTrustedStore(hCertPaths, pState->RootStore.m_hStore); if (RT_SUCCESS(rc)) { rc = RTCrX509CertPathsSetUntrustedStore(hCertPaths, pState->AdditionalStore.m_hStore); if (RT_SUCCESS(rc)) { rc = RTCrX509CertPathsSetUntrustedSet(hCertPaths, pCerts); if (RT_SUCCESS(rc)) { /* We don't technically need this, I think. */ rc = RTCrX509CertPathsSetTrustAnchorChecks(hCertPaths, true /*fEnable*/); if (RT_SUCCESS(rc)) { /* Build the paths: */ rc = RTCrX509CertPathsBuild(hCertPaths, RTErrInfoInitStatic(pStaticErrInfo)); if (RT_SUCCESS(rc)) { uint32_t const cPaths = RTCrX509CertPathsGetPathCount(hCertPaths); /* Validate the paths: */ uint32_t cValidPaths = 0; rc = RTCrX509CertPathsValidateAll(hCertPaths, &cValidPaths, RTErrInfoInitStatic(pStaticErrInfo)); if (RT_SUCCESS(rc)) { if (pState->cVerbosity > 0) RTMsgInfo("%u of %u paths are valid", cValidPaths, cPaths); if (pState->cVerbosity > 1) RTCrX509CertPathsDumpAll(hCertPaths, pState->cVerbosity, DumpToStdOutPrintfV, NULL); /* * Now, pick the first valid path with a real certificate at the end. */ for (uint32_t iPath = 0; iPath < cPaths; iPath++) { PCRTCRX509CERTIFICATE pRootCert = NULL; PCRTCRX509NAME pSubject = NULL; bool fTrusted = false; int rcVerify = -1; rc = RTCrX509CertPathsQueryPathInfo(hCertPaths, iPath, &fTrusted, NULL /*pcNodes*/, &pSubject, NULL, &pRootCert, NULL /*ppCertCtx*/, &rcVerify); if (RT_SUCCESS(rc)) { if (fTrusted && RT_SUCCESS(rcVerify) && pRootCert) { /* * Now copy out the certificate. */ rcExit = WriteCertToFile(pRootCert, pState->pszOut, pState->fForce); break; } } else { RTMsgError("RTCrX509CertPathsQueryPathInfo failed: %Rrc", rc); break; } } } else { RTMsgError("RTCrX509CertPathsValidateAll failed: %Rrc%#RTeim", rc, &pStaticErrInfo->Core); RTCrX509CertPathsDumpAll(hCertPaths, pState->cVerbosity, DumpToStdOutPrintfV, NULL); } } else RTMsgError("RTCrX509CertPathsBuild failed: %Rrc%#RTeim", rc, &pStaticErrInfo->Core); } else RTMsgError("RTCrX509CertPathsSetTrustAnchorChecks failed: %Rrc", rc); } else RTMsgError("RTCrX509CertPathsSetUntrustedSet failed: %Rrc", rc); } else RTMsgError("RTCrX509CertPathsSetUntrustedStore failed: %Rrc", rc); } else RTMsgError("RTCrX509CertPathsSetTrustedStore failed: %Rrc", rc); uint32_t cRefs = RTCrX509CertPathsRelease(hCertPaths); Assert(cRefs == 0); RT_NOREF(cRefs); return rcExit; } static RTEXITCODE RootExtractWorker(RootExtractState *pState, PRTERRINFOSTATIC pStaticErrInfo) { /* * Check that all we need is there and whether the output file exists. */ if (!pState->pszFile) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given."); if (!pState->pszOut) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No output file given."); if (!pState->fForce && RTPathExists(pState->pszOut)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "The output file '%s' exists.", pState->pszOut); /* * Detect the type of file we're dealing with, do type specific setup and * call common worker to do the rest. */ RTEXITCODE rcExit; RTSIGNTOOLFILETYPE enmFileType = DetectFileType(RTSIGNTOOLFILETYPE_DETECT, pState->pszFile); if (enmFileType == RTSIGNTOOLFILETYPE_EXE) { SIGNTOOLPKCS7EXE Exe; rcExit = SignToolPkcs7Exe_InitFromFile(&Exe, pState->pszFile, pState->cVerbosity, pState->enmLdrArch); if (rcExit == RTEXITCODE_SUCCESS) { rcExit = RootExtractWorker2(&Exe, pState, pStaticErrInfo); SignToolPkcs7Exe_Delete(&Exe); } } else if (enmFileType == RTSIGNTOOLFILETYPE_CAT) { SIGNTOOLPKCS7 Cat; rcExit = SignToolPkcs7_InitFromFile(&Cat, pState->pszFile, pState->cVerbosity); if (rcExit == RTEXITCODE_SUCCESS) { rcExit = RootExtractWorker2(&Cat, pState, pStaticErrInfo); SignToolPkcs7_Delete(&Cat); } } else rcExit = RTEXITCODE_FAILURE; return rcExit; } static RTEXITCODE HelpExtractRootCommon(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel, bool fTimestamp) { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "extract-%s-root [-v|--verbose] [-q|--quiet] [--signature-index|-i ] [--root ] " "[--self-signed-roots-from-system] [--additional ] " "[--input] [-f|--force] [--output|-o] \n", fTimestamp ? "timestamp" : "signer"); if (enmLevel == RTSIGNTOOLHELP_FULL) { RTStrmWrappedPrintf(pStrm, 0, "\n" "Extracts the root certificate of the %sgiven " "signature. If there are more than one valid certificate path, the first one with " "a full certificate will be picked.\n", fTimestamp ? "first timestamp associated with the " : ""); RTStrmWrappedPrintf(pStrm, 0, "\n" "Options:\n" " -v, --verbose, -q, --quite\n" " Controls the noise level. The '-v' options are accumlative while '-q' is absolute.\n" " Default: -q\n" " -i , --signature-index \n" " Zero-based index of the signature to extract the root for.\n" " Default: -i 0\n" " -r , --root \n" " Use the certificate(s) in the specified file as a trusted root(s). " "The file format can be PEM or DER.\n" " -R, --self-signed-roots-from-system\n" " Use all self-signed trusted root certificates found in the system and associated with the " "current user as trusted roots. This is limited to self-signed certificates, so that we get " "a full chain even if a non-end-entity certificate is present in any of those system stores for " "some reason.\n" " -a , --additional \n" " Use the certificate(s) in the specified file as a untrusted intermediate certificates. " "The file format can be PEM or DER.\n" " --input \n" " Signed executable or security cabinet file to examine. The '--input' option bit is optional " "and there to allow more flexible parameter ordering.\n" " -f, --force\n" " Overwrite existing output file. The default is not to overwriting any existing file.\n" " -o --output \n" " The name of the output file. Again the '-o|--output' bit is optional and only for flexibility.\n" ); } return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleExtractRootCommon(int cArgs, char **papszArgs, bool fTimestamp) { /* * Parse arguments. */ static const RTGETOPTDEF s_aOptions[] = { { "--root", 'r', RTGETOPT_REQ_STRING }, { "--self-signed-roots-from-system", 'R', RTGETOPT_REQ_NOTHING }, { "--additional", 'a', RTGETOPT_REQ_STRING }, { "--add", 'a', RTGETOPT_REQ_STRING }, { "--input", 'I', RTGETOPT_REQ_STRING }, { "--output", 'o', RTGETOPT_REQ_STRING }, { "--signature-index", 'i', RTGETOPT_REQ_UINT32 }, { "--force", 'f', RTGETOPT_REQ_NOTHING }, { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, }; RTERRINFOSTATIC StaticErrInfo; RootExtractState State(fTimestamp); if (!State.init()) return RTEXITCODE_FAILURE; RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); AssertRCReturn(rc, RTEXITCODE_FAILURE); RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion))) { switch (ch) { case 'a': if (!State.AdditionalStore.addFromFile(ValueUnion.psz, &StaticErrInfo)) return RTEXITCODE_FAILURE; break; case 'r': if (!State.RootStore.addFromFile(ValueUnion.psz, &StaticErrInfo)) return RTEXITCODE_FAILURE; break; case 'R': if (!State.RootStore.addSelfSignedRootsFromSystem(&StaticErrInfo)) return RTEXITCODE_FAILURE; break; case 'I': State.pszFile = ValueUnion.psz; break; case 'o': State.pszOut = ValueUnion.psz; break; case 'f': State.fForce = true; break; case 'i': State.iSignature = ValueUnion.u32; break; case 'v': State.cVerbosity++; break; case 'q': State.cVerbosity = 0; break; case 'V': return HandleVersion(cArgs, papszArgs); case 'h': return HelpExtractRootCommon(g_pStdOut, RTSIGNTOOLHELP_FULL, fTimestamp); case VINF_GETOPT_NOT_OPTION: if (!State.pszFile) State.pszFile = ValueUnion.psz; else if (!State.pszOut) State.pszOut = ValueUnion.psz; else return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many file arguments: %s", ValueUnion.psz); break; default: return RTGetOptPrintError(ch, &ValueUnion); } } return RootExtractWorker(&State, &StaticErrInfo); } static RTEXITCODE HelpExtractSignerRoot(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { return HelpExtractRootCommon(pStrm, enmLevel, false /*fTimestamp*/); } static RTEXITCODE HandleExtractSignerRoot(int cArgs, char **papszArgs) { return HandleExtractRootCommon(cArgs, papszArgs, false /*fTimestamp*/ ); } static RTEXITCODE HelpExtractTimestampRoot(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { return HelpExtractRootCommon(pStrm, enmLevel, true /*fTimestamp*/); } static RTEXITCODE HandleExtractTimestampRoot(int cArgs, char **papszArgs) { return HandleExtractRootCommon(cArgs, papszArgs, true /*fTimestamp*/ ); } /********************************************************************************************************************************* * The 'extract-exe-signature' command. * *********************************************************************************************************************************/ static RTEXITCODE HelpExtractExeSignature(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "extract-exe-signerature [--input|--exe|-e] [--output|-o] \n"); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleExtractExeSignature(int cArgs, char **papszArgs) { /* * Parse arguments. */ static const RTGETOPTDEF s_aOptions[] = { { "--exe", 'e', RTGETOPT_REQ_STRING }, { "--input", 'e', RTGETOPT_REQ_STRING }, { "--output", 'o', RTGETOPT_REQ_STRING }, { "--force", 'f', RTGETOPT_REQ_NOTHING }, }; const char *pszExe = NULL; const char *pszOut = NULL; RTLDRARCH enmLdrArch = RTLDRARCH_WHATEVER; unsigned cVerbosity = 0; bool fForce = false; RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); AssertRCReturn(rc, RTEXITCODE_FAILURE); RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion))) { switch (ch) { case 'e': pszExe = ValueUnion.psz; break; case 'o': pszOut = ValueUnion.psz; break; case 'f': fForce = true; break; case 'V': return HandleVersion(cArgs, papszArgs); case 'h': return HelpExtractExeSignerCert(g_pStdOut, RTSIGNTOOLHELP_FULL); case VINF_GETOPT_NOT_OPTION: if (!pszExe) pszExe = ValueUnion.psz; else if (!pszOut) pszOut = ValueUnion.psz; else return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many file arguments: %s", ValueUnion.psz); break; default: return RTGetOptPrintError(ch, &ValueUnion); } } if (!pszExe) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given."); if (!pszOut) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No output file given."); if (!fForce && RTPathExists(pszOut)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "The output file '%s' exists.", pszOut); /* * Do it. */ /* Read & decode the PKCS#7 signature. */ SIGNTOOLPKCS7EXE This; RTEXITCODE rcExit = SignToolPkcs7Exe_InitFromFile(&This, pszExe, cVerbosity, enmLdrArch); if (rcExit == RTEXITCODE_SUCCESS) { /* * Write out the PKCS#7 signature. */ RTFILE hFile; rc = RTFileOpen(&hFile, pszOut, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | (fForce ? RTFILE_O_CREATE_REPLACE : RTFILE_O_CREATE)); if (RT_SUCCESS(rc)) { rc = RTFileWrite(hFile, This.pbBuf, This.cbBuf, NULL); if (RT_SUCCESS(rc)) { rc = RTFileClose(hFile); if (RT_SUCCESS(rc)) { hFile = NIL_RTFILE; RTMsgInfo("Successfully wrote %u bytes to '%s'", This.cbBuf, pszOut); rcExit = RTEXITCODE_SUCCESS; } else RTMsgError("RTFileClose failed: %Rrc", rc); } else RTMsgError("RTFileWrite failed: %Rrc", rc); RTFileClose(hFile); } else RTMsgError("Error opening '%s' for writing: %Rrc", pszOut, rc); /* Delete the signature data. */ SignToolPkcs7Exe_Delete(&This); } return rcExit; } /********************************************************************************************************************************* * The 'add-nested-exe-signature' command. * *********************************************************************************************************************************/ static RTEXITCODE HelpAddNestedExeSignature(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "add-nested-exe-signature [-v|--verbose] [-d|--debug] [-p|--prepend] \n"); if (enmLevel == RTSIGNTOOLHELP_FULL) RTStrmWrappedPrintf(pStrm, 0, "\n" "The --debug option allows the source-exe to be omitted in order to test the " "encoding and PE file modification.\n" "\n" "The --prepend option puts the nested signature first rather than appending it " "to the end of of the nested signature set. Windows reads nested signatures in " "reverse order, so --prepend will logically putting it last.\n"); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleAddNestedExeSignature(int cArgs, char **papszArgs) { /* * Parse arguments. */ static const RTGETOPTDEF s_aOptions[] = { { "--prepend", 'p', RTGETOPT_REQ_NOTHING }, { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, { "--debug", 'd', RTGETOPT_REQ_NOTHING }, }; const char *pszDst = NULL; const char *pszSrc = NULL; unsigned cVerbosity = 0; bool fDebug = false; bool fPrepend = false; RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); AssertRCReturn(rc, RTEXITCODE_FAILURE); RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion))) { switch (ch) { case 'v': cVerbosity++; break; case 'd': fDebug = pszSrc == NULL; break; case 'p': fPrepend = true; break; case 'V': return HandleVersion(cArgs, papszArgs); case 'h': return HelpAddNestedExeSignature(g_pStdOut, RTSIGNTOOLHELP_FULL); case VINF_GETOPT_NOT_OPTION: if (!pszDst) pszDst = ValueUnion.psz; else if (!pszSrc) { pszSrc = ValueUnion.psz; fDebug = false; } else return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many file arguments: %s", ValueUnion.psz); break; default: return RTGetOptPrintError(ch, &ValueUnion); } } if (!pszDst) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No destination executable given."); if (!pszSrc && !fDebug) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No source executable file given."); /* * Do it. */ /* Read & decode the source PKCS#7 signature. */ SIGNTOOLPKCS7EXE Src; RTEXITCODE rcExit = pszSrc ? SignToolPkcs7Exe_InitFromFile(&Src, pszSrc, cVerbosity) : RTEXITCODE_SUCCESS; if (rcExit == RTEXITCODE_SUCCESS) { /* Ditto for the destination PKCS#7 signature. */ SIGNTOOLPKCS7EXE Dst; rcExit = SignToolPkcs7Exe_InitFromFile(&Dst, pszDst, cVerbosity); if (rcExit == RTEXITCODE_SUCCESS) { /* Do the signature manipulation. */ if (pszSrc) rcExit = SignToolPkcs7_AddNestedSignature(&Dst, &Src, cVerbosity, fPrepend); if (rcExit == RTEXITCODE_SUCCESS) rcExit = SignToolPkcs7_Encode(&Dst, cVerbosity); /* Update the destination executable file. */ if (rcExit == RTEXITCODE_SUCCESS) rcExit = SignToolPkcs7Exe_WriteSignatureToFile(&Dst, cVerbosity); SignToolPkcs7Exe_Delete(&Dst); } if (pszSrc) SignToolPkcs7Exe_Delete(&Src); } return rcExit; } /********************************************************************************************************************************* * The 'add-nested-cat-signature' command. * *********************************************************************************************************************************/ static RTEXITCODE HelpAddNestedCatSignature(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "add-nested-cat-signature [-v|--verbose] [-d|--debug] [-p|--prepend] \n"); if (enmLevel == RTSIGNTOOLHELP_FULL) RTStrmWrappedPrintf(pStrm, 0, "\n" "The --debug option allows the source-cat to be omitted in order to test the " "ASN.1 re-encoding of the destination catalog file.\n" "\n" "The --prepend option puts the nested signature first rather than appending it " "to the end of of the nested signature set. Windows reads nested signatures in " "reverse order, so --prepend will logically putting it last.\n"); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleAddNestedCatSignature(int cArgs, char **papszArgs) { /* * Parse arguments. */ static const RTGETOPTDEF s_aOptions[] = { { "--prepend", 'p', RTGETOPT_REQ_NOTHING }, { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, { "--debug", 'd', RTGETOPT_REQ_NOTHING }, }; const char *pszDst = NULL; const char *pszSrc = NULL; unsigned cVerbosity = 0; bool fDebug = false; bool fPrepend = false; RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); AssertRCReturn(rc, RTEXITCODE_FAILURE); RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion))) { switch (ch) { case 'v': cVerbosity++; break; case 'd': fDebug = pszSrc == NULL; break; case 'p': fPrepend = true; break; case 'V': return HandleVersion(cArgs, papszArgs); case 'h': return HelpAddNestedCatSignature(g_pStdOut, RTSIGNTOOLHELP_FULL); case VINF_GETOPT_NOT_OPTION: if (!pszDst) pszDst = ValueUnion.psz; else if (!pszSrc) { pszSrc = ValueUnion.psz; fDebug = false; } else return RTMsgErrorExit(RTEXITCODE_FAILURE, "Too many file arguments: %s", ValueUnion.psz); break; default: return RTGetOptPrintError(ch, &ValueUnion); } } if (!pszDst) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No destination catalog file given."); if (!pszSrc && !fDebug) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No source catalog file given."); /* * Do it. */ /* Read & decode the source PKCS#7 signature. */ SIGNTOOLPKCS7 Src; RTEXITCODE rcExit = pszSrc ? SignToolPkcs7_InitFromFile(&Src, pszSrc, cVerbosity) : RTEXITCODE_SUCCESS; if (rcExit == RTEXITCODE_SUCCESS) { /* Ditto for the destination PKCS#7 signature. */ SIGNTOOLPKCS7EXE Dst; rcExit = SignToolPkcs7_InitFromFile(&Dst, pszDst, cVerbosity); if (rcExit == RTEXITCODE_SUCCESS) { /* Do the signature manipulation. */ if (pszSrc) rcExit = SignToolPkcs7_AddNestedSignature(&Dst, &Src, cVerbosity, fPrepend); if (rcExit == RTEXITCODE_SUCCESS) rcExit = SignToolPkcs7_Encode(&Dst, cVerbosity); /* Update the destination executable file. */ if (rcExit == RTEXITCODE_SUCCESS) rcExit = SignToolPkcs7_WriteSignatureToFile(&Dst, pszDst, cVerbosity); SignToolPkcs7_Delete(&Dst); } if (pszSrc) SignToolPkcs7_Delete(&Src); } return rcExit; } /********************************************************************************************************************************* * The 'add-timestamp-exe-signature' command. * *********************************************************************************************************************************/ #ifndef IPRT_SIGNTOOL_NO_SIGNING static RTEXITCODE HelpAddTimestampExeSignature(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "add-timestamp-exe-signature [-v|--verbose] [--signature-index|-i ] " OPT_CERT_KEY_SYNOPSIS("--timestamp-", "") "[--timestamp-type old|new] " "[--timestamp-override ] " "[--replace-existing|-r] " "\n"); if (enmLevel == RTSIGNTOOLHELP_FULL) RTStrmWrappedPrintf(pStrm, 0, "This is mainly to test timestamp code.\n" "\n" "The --timestamp-override option can take a partial or full ISO timestamp. It is merged " "with the current time if partial.\n" "\n"); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleAddTimestampExeSignature(int cArgs, char **papszArgs) { /* * Parse arguments. */ static const RTGETOPTDEF s_aOptions[] = { { "--signature-index", 'i', RTGETOPT_REQ_UINT32 }, OPT_CERT_KEY_GETOPTDEF_ENTRIES("--timestamp-", "", 1000), { "--timestamp-type", OPT_TIMESTAMP_TYPE, RTGETOPT_REQ_STRING }, { "--timestamp-override", OPT_TIMESTAMP_OVERRIDE, RTGETOPT_REQ_STRING }, { "--replace-existing", 'r', RTGETOPT_REQ_NOTHING }, { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, }; unsigned cVerbosity = 0; unsigned iSignature = 0; bool fReplaceExisting = false; SignToolTimestampOpts TimestampOpts("timestamp"); RTTIMESPEC SigningTime; RTTimeNow(&SigningTime); RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); AssertRCReturn(rc, RTEXITCODE_FAILURE); RTEXITCODE rcExit = RTEXITCODE_SUCCESS; RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion))) { RTEXITCODE rcExit2 = RTEXITCODE_SUCCESS; switch (ch) { OPT_CERT_KEY_SWITCH_CASES(TimestampOpts, 1000, ch, ValueUnion, rcExit2); case 'i': iSignature = ValueUnion.u32; break; case OPT_TIMESTAMP_TYPE: rcExit2 = HandleOptTimestampType(&TimestampOpts, ValueUnion.psz); break; case OPT_TIMESTAMP_OVERRIDE: rcExit2 = HandleOptTimestampOverride(&SigningTime, ValueUnion.psz); break; case 'r': fReplaceExisting = true; break; case 'v': cVerbosity++; break; case 'V': return HandleVersion(cArgs, papszArgs); case 'h': return HelpAddTimestampExeSignature(g_pStdOut, RTSIGNTOOLHELP_FULL); case VINF_GETOPT_NOT_OPTION: /* Do final certificate and key option processing (first file only). */ rcExit2 = TimestampOpts.finalizeOptions(cVerbosity); if (rcExit2 == RTEXITCODE_SUCCESS) { /* Do the work: */ SIGNTOOLPKCS7EXE Exe; rcExit2 = SignToolPkcs7Exe_InitFromFile(&Exe, ValueUnion.psz, cVerbosity); if (rcExit2 == RTEXITCODE_SUCCESS) { rcExit2 = SignToolPkcs7_AddTimestampSignature(&Exe, cVerbosity, iSignature, fReplaceExisting, SigningTime, &TimestampOpts); if (rcExit2 == RTEXITCODE_SUCCESS) rcExit2 = SignToolPkcs7_Encode(&Exe, cVerbosity); if (rcExit2 == RTEXITCODE_SUCCESS) rcExit2 = SignToolPkcs7Exe_WriteSignatureToFile(&Exe, cVerbosity); SignToolPkcs7Exe_Delete(&Exe); } if (rcExit2 != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS) rcExit = rcExit2; rcExit2 = RTEXITCODE_SUCCESS; } break; default: return RTGetOptPrintError(ch, &ValueUnion); } if (rcExit2 != RTEXITCODE_SUCCESS) { rcExit = rcExit2; break; } } return rcExit; } #endif /*!IPRT_SIGNTOOL_NO_SIGNING */ /********************************************************************************************************************************* * The 'sign-exe' command. * *********************************************************************************************************************************/ #ifndef IPRT_SIGNTOOL_NO_SIGNING static RTEXITCODE HelpSign(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "sign [-v|--verbose] " "[--file-type exe|cat] " "[--type|/fd sha1|sha256] " "[--hash-pages|/ph] " "[--no-hash-pages|/nph] " "[--append/as] " "[--no-signing-time] " "[--add-cert ] " "[--timestamp-type old|new] " "[--timestamp-override ] " "[--verbose|/debug|-v] " OPT_CERT_KEY_SYNOPSIS("--", "") OPT_CERT_KEY_SYNOPSIS("--timestamp-", "") //OPT_CERT_KEY_SYNOPSIS("--timestamp-", "-2") - doesn't work, windows only uses one. Check again with new-style signatures "\n"); if (enmLevel == RTSIGNTOOLHELP_FULL) RTStrmWrappedPrintf(pStrm, 0, "\n" "Create a new code signature for an executable or catalog.\n" "\n" "Options:\n" " --append, /as\n" " Append the signature if one already exists. The default is to replace any existing signature.\n" " --type sha1|sha256, /fd sha1|sha256\n" " Signature type, SHA-1 or SHA-256.\n" " --hash-pages, /ph, --no-page-hashes, /nph\n" " Enables or disables page hashing. Ignored for catalog files. Default: --no-page-hashes\n" " --add-cert , /ac \n" " Adds (first) certificate from the file to the signature. Both PEM and DER (binary) encodings " "are accepted. Repeat to add more certiifcates.\n" " --timestamp-override \n" " This specifies the signing time as a ISO timestamp. Partial timestamps are merged with the " "current time. This is applied to any timestamp signature as well as the signingTime attribute of " "main signature. Higher resolution than seconds is not supported. Default: Current time.\n" " --no-signing-time\n" " Don't set the signing time on the main signature, only on the timestamp one. Unfortunately, " "this doesn't work without modifying OpenSSL a little.\n" " --timestamp-type old|new\n" " Selects the timstamp type. 'old' is the old style /t stuff from signtool.exe. " "'new' means a RTC-3161 timstamp - currently not implemented. Default: old\n" //" --timestamp-type-2 old|new\n" //" Same as --timestamp-type but for the 2nd timstamp signature.\n" "\n" //"Certificate and Key Options (--timestamp-cert-name[-2] etc for timestamps):\n" "Certificate and Key Options (--timestamp-cert-name etc for timestamps):\n" " --cert-subject , /n \n" " Locate the main signature signing certificate and key, unless anything else is given, " "by the given name substring. Overrides any previous --cert-sha1 and --cert-file options.\n" " --cert-sha1 , /sha1 \n" " Locate the main signature signing certificate and key, unless anything else is given, " "by the given thumbprint. The hex bytes can be space separated, colon separated, just " "bunched together, or a mix of these. This overrids any previous --cert-name and --cert-file " "options.\n" " --cert-store , /s \n" " Certificate store to search when using --cert-name or --cert-sha1. Default: MY\n" " --cert-machine-store, /sm\n" " Use the machine store rather the ones of the current user.\n" " --cert-file , /f \n" " Load the certificate and key, unless anything else is given, from given file. Both PEM and " "DER (binary) encodings are supported. Keys file can be RSA or PKCS#12 formatted.\n" " --key-file \n" " Load the private key from the given file. Support RSA and PKCS#12 formatted files.\n" " --key-password , /p \n" " Password to use to decrypt a PKCS#12 password file.\n" " --key-password-file |stdin\n" " Load password to decrypt the password file from the given file or from stdin.\n" " --key-name , /kc \n" " The private key container name. Not implemented.\n" " --key-provider , /csp \n" " The name of the crypto provider where the private key conatiner specified via --key-name " "can be found.\n" ); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleSign(int cArgs, char **papszArgs) { /* * Parse arguments. */ static const RTGETOPTDEF s_aOptions[] = { { "--append", 'A', RTGETOPT_REQ_NOTHING }, { "/as", 'A', RTGETOPT_REQ_NOTHING }, { "/a", OPT_IGNORED, RTGETOPT_REQ_NOTHING }, /* select best cert automatically */ { "--type", 't', RTGETOPT_REQ_STRING }, { "/fd", 't', RTGETOPT_REQ_STRING }, { "--hash-pages", OPT_HASH_PAGES, RTGETOPT_REQ_NOTHING }, { "/ph", OPT_HASH_PAGES, RTGETOPT_REQ_NOTHING }, { "--no-hash-pages", OPT_NO_HASH_PAGES, RTGETOPT_REQ_NOTHING }, { "/nph", OPT_NO_HASH_PAGES, RTGETOPT_REQ_NOTHING }, { "--add-cert", OPT_ADD_CERT, RTGETOPT_REQ_STRING }, { "/ac", OPT_ADD_CERT, RTGETOPT_REQ_STRING }, { "--description", 'd', RTGETOPT_REQ_STRING }, { "--desc", 'd', RTGETOPT_REQ_STRING }, { "/d", 'd', RTGETOPT_REQ_STRING }, { "--description-url", 'D', RTGETOPT_REQ_STRING }, { "--desc-url", 'D', RTGETOPT_REQ_STRING }, { "/du", 'D', RTGETOPT_REQ_STRING }, { "--no-signing-time", OPT_NO_SIGNING_TIME, RTGETOPT_REQ_NOTHING }, OPT_CERT_KEY_GETOPTDEF_ENTRIES("--", "", 1000), OPT_CERT_KEY_GETOPTDEF_COMPAT_ENTRIES( 1000), OPT_CERT_KEY_GETOPTDEF_ENTRIES("--timestamp-", "", 1020), //OPT_CERT_KEY_GETOPTDEF_ENTRIES("--timestamp-", "-1", 1020), //OPT_CERT_KEY_GETOPTDEF_ENTRIES("--timestamp-", "-2", 1040), - disabled as windows cannot make use of it. Try again when // new-style timestamp signatures has been implemented. Otherwise, just add two primary signatures with the two // different timestamps certificates / hashes / whatever. { "--timestamp-type", OPT_TIMESTAMP_TYPE, RTGETOPT_REQ_STRING }, { "--timestamp-type-1", OPT_TIMESTAMP_TYPE, RTGETOPT_REQ_STRING }, { "--timestamp-type-2", OPT_TIMESTAMP_TYPE_2, RTGETOPT_REQ_STRING }, { "--timestamp-override", OPT_TIMESTAMP_OVERRIDE, RTGETOPT_REQ_STRING }, { "--file-type", OPT_FILE_TYPE, RTGETOPT_REQ_STRING }, { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, { "/v", 'v', RTGETOPT_REQ_NOTHING }, { "/debug", 'v', RTGETOPT_REQ_NOTHING }, }; unsigned cVerbosity = 0; RTDIGESTTYPE enmSigType = RTDIGESTTYPE_SHA1; bool fReplaceExisting = true; bool fHashPages = false; bool fNoSigningTime = false; RTSIGNTOOLFILETYPE enmForceFileType = RTSIGNTOOLFILETYPE_DETECT; SignToolKeyPair SigningCertKey("signing", true); CryptoStore AddCerts; const char *pszDescription = NULL; /** @todo implement putting descriptions into the OpusInfo stuff. */ const char *pszDescriptionUrl = NULL; SignToolTimestampOpts aTimestampOpts[2] = { SignToolTimestampOpts("timestamp"), SignToolTimestampOpts("timestamp#2") }; RTTIMESPEC SigningTime; RTTimeNow(&SigningTime); RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); AssertRCReturn(rc, RTEXITCODE_FAILURE); RTEXITCODE rcExit = RTEXITCODE_SUCCESS; RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion))) { RTEXITCODE rcExit2 = RTEXITCODE_SUCCESS; switch (ch) { OPT_CERT_KEY_SWITCH_CASES(SigningCertKey, 1000, ch, ValueUnion, rcExit2); OPT_CERT_KEY_SWITCH_CASES(aTimestampOpts[0], 1020, ch, ValueUnion, rcExit2); OPT_CERT_KEY_SWITCH_CASES(aTimestampOpts[1], 1040, ch, ValueUnion, rcExit2); case 't': rcExit2 = HandleOptSignatureType(&enmSigType, ValueUnion.psz); break; case 'A': fReplaceExisting = false; break; case 'd': pszDescription = ValueUnion.psz; break; case 'D': pszDescriptionUrl = ValueUnion.psz; break; case OPT_HASH_PAGES: fHashPages = true; break; case OPT_NO_HASH_PAGES: fHashPages = false; break; case OPT_NO_SIGNING_TIME: fNoSigningTime = true; break; case OPT_ADD_CERT: rcExit2 = HandleOptAddCert(&AddCerts.m_hStore, ValueUnion.psz); break; case OPT_TIMESTAMP_TYPE: rcExit2 = HandleOptTimestampType(&aTimestampOpts[0], ValueUnion.psz); break; case OPT_TIMESTAMP_TYPE_2: rcExit2 = HandleOptTimestampType(&aTimestampOpts[1], ValueUnion.psz); break; case OPT_TIMESTAMP_OVERRIDE: rcExit2 = HandleOptTimestampOverride(&SigningTime, ValueUnion.psz); break; case OPT_FILE_TYPE: rcExit2 = HandleOptFileType(&enmForceFileType, ValueUnion.psz); break; case OPT_IGNORED: break; case 'v': cVerbosity++; break; case 'V': return HandleVersion(cArgs, papszArgs); case 'h': return HelpSign(g_pStdOut, RTSIGNTOOLHELP_FULL); case VINF_GETOPT_NOT_OPTION: /* * Do final certificate and key option processing (first file only). */ rcExit2 = SigningCertKey.finalizeOptions(cVerbosity); for (unsigned i = 0; rcExit2 == RTEXITCODE_SUCCESS && i < RT_ELEMENTS(aTimestampOpts); i++) rcExit2 = aTimestampOpts[i].finalizeOptions(cVerbosity); if (rcExit2 == RTEXITCODE_SUCCESS) { /* * Detect file type. */ RTSIGNTOOLFILETYPE enmFileType = DetectFileType(enmForceFileType, ValueUnion.psz); if (enmFileType == RTSIGNTOOLFILETYPE_EXE) { /* * Sign executable image. */ SIGNTOOLPKCS7EXE Exe; rcExit2 = SignToolPkcs7Exe_InitFromFile(&Exe, ValueUnion.psz, cVerbosity, RTLDRARCH_WHATEVER, true /*fAllowUnsigned*/); if (rcExit2 == RTEXITCODE_SUCCESS) { rcExit2 = SignToolPkcs7_AddOrReplaceSignature(&Exe, cVerbosity, enmSigType, fReplaceExisting, fHashPages, fNoSigningTime, &SigningCertKey, AddCerts.m_hStore, SigningTime, RT_ELEMENTS(aTimestampOpts), aTimestampOpts); if (rcExit2 == RTEXITCODE_SUCCESS) rcExit2 = SignToolPkcs7_Encode(&Exe, cVerbosity); if (rcExit2 == RTEXITCODE_SUCCESS) rcExit2 = SignToolPkcs7Exe_WriteSignatureToFile(&Exe, cVerbosity); SignToolPkcs7Exe_Delete(&Exe); } } else if (enmFileType == RTSIGNTOOLFILETYPE_CAT) { /* * Sign catalog file. */ SIGNTOOLPKCS7 Cat; rcExit2 = SignToolPkcs7_InitFromFile(&Cat, ValueUnion.psz, cVerbosity); if (rcExit2 == RTEXITCODE_SUCCESS) { rcExit2 = SignToolPkcs7_AddOrReplaceCatSignature(&Cat, cVerbosity, enmSigType, fReplaceExisting, fNoSigningTime, &SigningCertKey, AddCerts.m_hStore, SigningTime, RT_ELEMENTS(aTimestampOpts), aTimestampOpts); if (rcExit2 == RTEXITCODE_SUCCESS) rcExit2 = SignToolPkcs7_Encode(&Cat, cVerbosity); if (rcExit2 == RTEXITCODE_SUCCESS) rcExit2 = SignToolPkcs7_WriteSignatureToFile(&Cat, ValueUnion.psz, cVerbosity); SignToolPkcs7_Delete(&Cat); } } else rcExit2 = RTEXITCODE_FAILURE; if (rcExit2 != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS) rcExit = rcExit2; rcExit2 = RTEXITCODE_SUCCESS; } break; default: return RTGetOptPrintError(ch, &ValueUnion); } if (rcExit2 != RTEXITCODE_SUCCESS) { rcExit = rcExit2; break; } } return rcExit; } #endif /*!IPRT_SIGNTOOL_NO_SIGNING */ /********************************************************************************************************************************* * The 'verify-exe' command. * *********************************************************************************************************************************/ #ifndef IPRT_IN_BUILD_TOOL static RTEXITCODE HelpVerifyExe(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "verify-exe [--verbose|--quiet] [--kernel] [--root ] [--self-signed-roots-from-system] " "[--additional ] [--type ] [exe2 [..]]\n"); return RTEXITCODE_SUCCESS; } typedef struct VERIFYEXESTATE { CryptoStore RootStore; CryptoStore KernelRootStore; CryptoStore AdditionalStore; bool fKernel; int cVerbose; enum { kSignType_Windows, kSignType_OSX } enmSignType; RTLDRARCH enmLdrArch; uint32_t cBad; uint32_t cOkay; const char *pszFilename; RTTIMESPEC ValidationTime; VERIFYEXESTATE() : fKernel(false) , cVerbose(0) , enmSignType(kSignType_Windows) , enmLdrArch(RTLDRARCH_WHATEVER) , cBad(0) , cOkay(0) , pszFilename(NULL) { RTTimeSpecSetSeconds(&ValidationTime, 0); } } VERIFYEXESTATE; # ifdef VBOX /** Certificate store load set. * Declared outside HandleVerifyExe because of braindead gcc visibility crap. */ struct STSTORESET { RTCRSTORE hStore; PCSUPTAENTRY paTAs; unsigned cTAs; }; # endif /** * @callback_method_impl{FNRTCRPKCS7VERIFYCERTCALLBACK, * Standard code signing. Use this for Microsoft SPC.} */ static DECLCALLBACK(int) VerifyExecCertVerifyCallback(PCRTCRX509CERTIFICATE pCert, RTCRX509CERTPATHS hCertPaths, uint32_t fFlags, void *pvUser, PRTERRINFO pErrInfo) { VERIFYEXESTATE *pState = (VERIFYEXESTATE *)pvUser; uint32_t cPaths = RTCrX509CertPathsGetPathCount(hCertPaths); /* * Dump all the paths. */ if (pState->cVerbose > 0) { RTPrintf(fFlags & RTCRPKCS7VCC_F_TIMESTAMP ? "Timestamp Path%s:\n" : "Signature Path%s:\n", cPaths == 1 ? "" : "s"); for (uint32_t iPath = 0; iPath < cPaths; iPath++) { //if (iPath != 0) // RTPrintf("---\n"); RTCrX509CertPathsDumpOne(hCertPaths, iPath, pState->cVerbose, RTStrmDumpPrintfV, g_pStdOut); *pErrInfo->pszMsg = '\0'; } //RTPrintf(fFlags & RTCRPKCS7VCC_F_TIMESTAMP ? "--- end timestamp ---\n" : "--- end signature ---\n"); } /* * Test signing certificates normally doesn't have all the necessary * features required below. So, treat them as special cases. */ if ( hCertPaths == NIL_RTCRX509CERTPATHS && RTCrX509Name_Compare(&pCert->TbsCertificate.Issuer, &pCert->TbsCertificate.Subject) == 0) { RTMsgInfo("Test signed.\n"); return VINF_SUCCESS; } if (hCertPaths == NIL_RTCRX509CERTPATHS) RTMsgInfo("Signed by trusted certificate.\n"); /* * Standard code signing capabilites required. */ int rc = RTCrPkcs7VerifyCertCallbackCodeSigning(pCert, hCertPaths, fFlags, NULL, pErrInfo); if ( RT_SUCCESS(rc) && (fFlags & RTCRPKCS7VCC_F_SIGNED_DATA)) { /* * If windows kernel signing, a valid certificate path must be anchored * by the microsoft kernel signing root certificate. The only * alternative is test signing. */ if ( pState->fKernel && hCertPaths != NIL_RTCRX509CERTPATHS && pState->enmSignType == VERIFYEXESTATE::kSignType_Windows) { uint32_t cFound = 0; uint32_t cValid = 0; for (uint32_t iPath = 0; iPath < cPaths; iPath++) { bool fTrusted; PCRTCRX509NAME pSubject; PCRTCRX509SUBJECTPUBLICKEYINFO pPublicKeyInfo; int rcVerify; rc = RTCrX509CertPathsQueryPathInfo(hCertPaths, iPath, &fTrusted, NULL /*pcNodes*/, &pSubject, &pPublicKeyInfo, NULL, NULL /*pCertCtx*/, &rcVerify); AssertRCBreak(rc); if (RT_SUCCESS(rcVerify)) { Assert(fTrusted); cValid++; /* Search the kernel signing root store for a matching anchor. */ RTCRSTORECERTSEARCH Search; rc = RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280(pState->KernelRootStore.m_hStore, pSubject, &Search); AssertRCBreak(rc); PCRTCRCERTCTX pCertCtx; while ((pCertCtx = RTCrStoreCertSearchNext(pState->KernelRootStore.m_hStore, &Search)) != NULL) { PCRTCRX509SUBJECTPUBLICKEYINFO pPubKeyInfo; if (pCertCtx->pCert) pPubKeyInfo = &pCertCtx->pCert->TbsCertificate.SubjectPublicKeyInfo; else if (pCertCtx->pTaInfo) pPubKeyInfo = &pCertCtx->pTaInfo->PubKey; else pPubKeyInfo = NULL; if (RTCrX509SubjectPublicKeyInfo_Compare(pPubKeyInfo, pPublicKeyInfo) == 0) cFound++; RTCrCertCtxRelease(pCertCtx); } int rc2 = RTCrStoreCertSearchDestroy(pState->KernelRootStore.m_hStore, &Search); AssertRC(rc2); } } if (RT_SUCCESS(rc) && cFound == 0) rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, "Not valid kernel code signature."); if (RT_SUCCESS(rc) && cValid != 2) RTMsgWarning("%u valid paths, expected 2", cValid); } /* * For Mac OS X signing, check for special developer ID attributes. */ else if (pState->enmSignType == VERIFYEXESTATE::kSignType_OSX) { uint32_t cDevIdApp = 0; uint32_t cDevIdKext = 0; uint32_t cDevIdMacDev = 0; for (uint32_t i = 0; i < pCert->TbsCertificate.T3.Extensions.cItems; i++) { PCRTCRX509EXTENSION pExt = pCert->TbsCertificate.T3.Extensions.papItems[i]; if (RTAsn1ObjId_CompareWithString(&pExt->ExtnId, RTCR_APPLE_CS_DEVID_APPLICATION_OID) == 0) { cDevIdApp++; if (!pExt->Critical.fValue) rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, "Dev ID Application certificate extension is not flagged critical"); } else if (RTAsn1ObjId_CompareWithString(&pExt->ExtnId, RTCR_APPLE_CS_DEVID_KEXT_OID) == 0) { cDevIdKext++; if (!pExt->Critical.fValue) rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, "Dev ID kext certificate extension is not flagged critical"); } else if (RTAsn1ObjId_CompareWithString(&pExt->ExtnId, RTCR_APPLE_CS_DEVID_MAC_SW_DEV_OID) == 0) { cDevIdMacDev++; if (!pExt->Critical.fValue) rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, "Dev ID Mac SW dev certificate extension is not flagged critical"); } } if (cDevIdApp == 0) { if (cDevIdMacDev == 0) rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, "Certificate is missing the 'Dev ID Application' extension"); else RTMsgWarning("Mac SW dev certificate used to sign code."); } if (cDevIdKext == 0 && pState->fKernel) { if (cDevIdMacDev == 0) rc = RTErrInfoSetF(pErrInfo, VERR_GENERAL_FAILURE, "Certificate is missing the 'Dev ID kext' extension"); else RTMsgWarning("Mac SW dev certificate used to sign kernel code."); } } } return rc; } /** @callback_method_impl{FNRTLDRVALIDATESIGNEDDATA} */ static DECLCALLBACK(int) VerifyExeCallback(RTLDRMOD hLdrMod, PCRTLDRSIGNATUREINFO pInfo, PRTERRINFO pErrInfo, void *pvUser) { VERIFYEXESTATE *pState = (VERIFYEXESTATE *)pvUser; RT_NOREF_PV(hLdrMod); switch (pInfo->enmType) { case RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA: { PCRTCRPKCS7CONTENTINFO pContentInfo = (PCRTCRPKCS7CONTENTINFO)pInfo->pvSignature; if (pState->cVerbose > 0) RTMsgInfo("Verifying '%s' signature #%u ...\n", pState->pszFilename, pInfo->iSignature + 1); /* * Dump the signed data if so requested and it's the first one, assuming that * additional signatures in contained wihtin the same ContentInfo structure. */ if (pState->cVerbose > 1 && pInfo->iSignature == 0) RTAsn1Dump(&pContentInfo->SeqCore.Asn1Core, 0, 0, RTStrmDumpPrintfV, g_pStdOut); /* * We'll try different alternative timestamps here. */ struct { RTTIMESPEC TimeSpec; const char *pszDesc; } aTimes[3]; unsigned cTimes = 0; /* The specified timestamp. */ if (RTTimeSpecGetSeconds(&pState->ValidationTime) != 0) { aTimes[cTimes].TimeSpec = pState->ValidationTime; aTimes[cTimes].pszDesc = "validation time"; cTimes++; } /* Linking timestamp: */ uint64_t uLinkingTime = 0; int rc = RTLdrQueryProp(hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uLinkingTime, sizeof(uLinkingTime)); if (RT_SUCCESS(rc)) { RTTimeSpecSetSeconds(&aTimes[cTimes].TimeSpec, uLinkingTime); aTimes[cTimes].pszDesc = "at link time"; cTimes++; } else if (rc != VERR_NOT_FOUND) RTMsgError("RTLdrQueryProp/RTLDRPROP_TIMESTAMP_SECONDS failed on '%s': %Rrc\n", pState->pszFilename, rc); /* Now: */ RTTimeNow(&aTimes[cTimes].TimeSpec); aTimes[cTimes].pszDesc = "now"; cTimes++; /* * Do the actual verification. */ for (unsigned iTime = 0; iTime < cTimes; iTime++) { if (pInfo->pvExternalData) rc = RTCrPkcs7VerifySignedDataWithExternalData(pContentInfo, RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_MS_TIMESTAMP_IF_PRESENT | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS, pState->AdditionalStore.m_hStore, pState->RootStore.m_hStore, &aTimes[iTime].TimeSpec, VerifyExecCertVerifyCallback, pState, pInfo->pvExternalData, pInfo->cbExternalData, pErrInfo); else rc = RTCrPkcs7VerifySignedData(pContentInfo, RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT | RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_MS_TIMESTAMP_IF_PRESENT | RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS, pState->AdditionalStore.m_hStore, pState->RootStore.m_hStore, &aTimes[iTime].TimeSpec, VerifyExecCertVerifyCallback, pState, pErrInfo); if (RT_SUCCESS(rc)) { Assert(rc == VINF_SUCCESS || rc == VINF_CR_DIGEST_DEPRECATED); const char *pszNote = rc == VINF_CR_DIGEST_DEPRECATED ? " (deprecated digest)" : ""; if (pInfo->cSignatures == 1) RTMsgInfo("'%s' is valid %s%s.\n", pState->pszFilename, aTimes[iTime].pszDesc, pszNote); else RTMsgInfo("'%s' signature #%u is valid %s%s.\n", pState->pszFilename, pInfo->iSignature + 1, aTimes[iTime].pszDesc, pszNote); pState->cOkay++; return VINF_SUCCESS; } if (rc != VERR_CR_X509_CPV_NOT_VALID_AT_TIME) { if (pInfo->cSignatures == 1) RTMsgError("%s: Failed to verify signature: %Rrc%#RTeim\n", pState->pszFilename, rc, pErrInfo); else RTMsgError("%s: Failed to verify signature #%u: %Rrc%#RTeim\n", pState->pszFilename, pInfo->iSignature + 1, rc, pErrInfo); pState->cBad++; return VINF_SUCCESS; } } if (pInfo->cSignatures == 1) RTMsgError("%s: Signature is not valid at present or link time.\n", pState->pszFilename); else RTMsgError("%s: Signature #%u is not valid at present or link time.\n", pState->pszFilename, pInfo->iSignature + 1); pState->cBad++; return VINF_SUCCESS; } default: return RTErrInfoSetF(pErrInfo, VERR_NOT_SUPPORTED, "Unsupported signature type: %d", pInfo->enmType); } } /** * Worker for HandleVerifyExe. */ static RTEXITCODE HandleVerifyExeWorker(VERIFYEXESTATE *pState, const char *pszFilename, PRTERRINFOSTATIC pStaticErrInfo) { /* * Open the executable image and verify it. */ RTLDRMOD hLdrMod; int rc = RTLdrOpen(pszFilename, RTLDR_O_FOR_VALIDATION, pState->enmLdrArch, &hLdrMod); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error opening executable image '%s': %Rrc", pszFilename, rc); /* Reset the state. */ pState->cBad = 0; pState->cOkay = 0; pState->pszFilename = pszFilename; rc = RTLdrVerifySignature(hLdrMod, VerifyExeCallback, pState, RTErrInfoInitStatic(pStaticErrInfo)); if (RT_FAILURE(rc)) RTMsgError("RTLdrVerifySignature failed on '%s': %Rrc - %s\n", pszFilename, rc, pStaticErrInfo->szMsg); int rc2 = RTLdrClose(hLdrMod); if (RT_FAILURE(rc2)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTLdrClose failed: %Rrc\n", rc2); if (RT_FAILURE(rc)) return rc != VERR_LDRVI_NOT_SIGNED ? RTEXITCODE_FAILURE : RTEXITCODE_SKIPPED; return pState->cOkay > 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; } static RTEXITCODE HandleVerifyExe(int cArgs, char **papszArgs) { RTERRINFOSTATIC StaticErrInfo; /* * Parse arguments. */ static const RTGETOPTDEF s_aOptions[] = { { "--kernel", 'k', RTGETOPT_REQ_NOTHING }, { "--root", 'r', RTGETOPT_REQ_STRING }, { "--self-signed-roots-from-system", 'R', RTGETOPT_REQ_NOTHING }, { "--additional", 'a', RTGETOPT_REQ_STRING }, { "--add", 'a', RTGETOPT_REQ_STRING }, { "--type", 't', RTGETOPT_REQ_STRING }, { "--validation-time", 'T', RTGETOPT_REQ_STRING }, { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, }; VERIFYEXESTATE State; int rc = RTCrStoreCreateInMem(&State.RootStore.m_hStore, 0); if (RT_SUCCESS(rc)) rc = RTCrStoreCreateInMem(&State.KernelRootStore.m_hStore, 0); if (RT_SUCCESS(rc)) rc = RTCrStoreCreateInMem(&State.AdditionalStore.m_hStore, 0); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error creating in-memory certificate store: %Rrc", rc); RTGETOPTSTATE GetState; rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); AssertRCReturn(rc, RTEXITCODE_FAILURE); RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion)) && ch != VINF_GETOPT_NOT_OPTION) { switch (ch) { case 'a': if (!State.AdditionalStore.addFromFile(ValueUnion.psz, &StaticErrInfo)) return RTEXITCODE_FAILURE; break; case 'r': if (!State.RootStore.addFromFile(ValueUnion.psz, &StaticErrInfo)) return RTEXITCODE_FAILURE; break; case 'R': if (!State.RootStore.addSelfSignedRootsFromSystem(&StaticErrInfo)) return RTEXITCODE_FAILURE; break; case 't': if (!strcmp(ValueUnion.psz, "win") || !strcmp(ValueUnion.psz, "windows")) State.enmSignType = VERIFYEXESTATE::kSignType_Windows; else if (!strcmp(ValueUnion.psz, "osx") || !strcmp(ValueUnion.psz, "apple")) State.enmSignType = VERIFYEXESTATE::kSignType_OSX; else return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown signing type: '%s'", ValueUnion.psz); break; case 'T': if (!RTTimeSpecFromString(&State.ValidationTime, ValueUnion.psz)) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid validation time (%s): %Rrc", ValueUnion.psz, rc); break; case 'k': State.fKernel = true; break; case 'v': State.cVerbose++; break; case 'q': State.cVerbose = 0; break; case 'V': return HandleVersion(cArgs, papszArgs); case 'h': return HelpVerifyExe(g_pStdOut, RTSIGNTOOLHELP_FULL); default: return RTGetOptPrintError(ch, &ValueUnion); } } if (ch != VINF_GETOPT_NOT_OPTION) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given."); /* * Populate the certificate stores according to the signing type. */ # ifdef VBOX unsigned cSets = 0; struct STSTORESET aSets[6]; switch (State.enmSignType) { case VERIFYEXESTATE::kSignType_Windows: aSets[cSets].hStore = State.RootStore.m_hStore; aSets[cSets].paTAs = g_aSUPTimestampTAs; aSets[cSets].cTAs = g_cSUPTimestampTAs; cSets++; aSets[cSets].hStore = State.RootStore.m_hStore; aSets[cSets].paTAs = g_aSUPSpcRootTAs; aSets[cSets].cTAs = g_cSUPSpcRootTAs; cSets++; aSets[cSets].hStore = State.RootStore.m_hStore; aSets[cSets].paTAs = g_aSUPNtKernelRootTAs; aSets[cSets].cTAs = g_cSUPNtKernelRootTAs; cSets++; aSets[cSets].hStore = State.KernelRootStore.m_hStore; aSets[cSets].paTAs = g_aSUPNtKernelRootTAs; aSets[cSets].cTAs = g_cSUPNtKernelRootTAs; cSets++; break; case VERIFYEXESTATE::kSignType_OSX: aSets[cSets].hStore = State.RootStore.m_hStore; aSets[cSets].paTAs = g_aSUPAppleRootTAs; aSets[cSets].cTAs = g_cSUPAppleRootTAs; cSets++; break; } for (unsigned i = 0; i < cSets; i++) for (unsigned j = 0; j < aSets[i].cTAs; j++) { rc = RTCrStoreCertAddEncoded(aSets[i].hStore, RTCRCERTCTX_F_ENC_TAF_DER, aSets[i].paTAs[j].pch, aSets[i].paTAs[j].cb, RTErrInfoInitStatic(&StaticErrInfo)); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTCrStoreCertAddEncoded failed (%u/%u): %s", i, j, StaticErrInfo.szMsg); } # endif /* VBOX */ /* * Do it. */ RTEXITCODE rcExit; for (;;) { rcExit = HandleVerifyExeWorker(&State, ValueUnion.psz, &StaticErrInfo); if (rcExit != RTEXITCODE_SUCCESS) break; /* * Next file */ ch = RTGetOpt(&GetState, &ValueUnion); if (ch == 0) break; if (ch != VINF_GETOPT_NOT_OPTION) { rcExit = RTGetOptPrintError(ch, &ValueUnion); break; } } return rcExit; } #endif /* !IPRT_IN_BUILD_TOOL */ /* * common code for show-exe and show-cat: */ /** * Display an object ID. * * @returns IPRT status code. * @param pThis The show exe instance data. * @param pObjId The object ID to display. * @param pszLabel The field label (prefixed by szPrefix). * @param pszPost What to print after the ID (typically newline). */ static void HandleShowExeWorkerDisplayObjId(PSHOWEXEPKCS7 pThis, PCRTASN1OBJID pObjId, const char *pszLabel, const char *pszPost) { int rc = RTAsn1QueryObjIdName(pObjId, pThis->szTmp, sizeof(pThis->szTmp)); if (RT_SUCCESS(rc)) { if (pThis->cVerbosity > 1) RTPrintf("%s%s%s (%s)%s", pThis->szPrefix, pszLabel, pThis->szTmp, pObjId->szObjId, pszPost); else RTPrintf("%s%s%s%s", pThis->szPrefix, pszLabel, pThis->szTmp, pszPost); } else RTPrintf("%s%s%s%s", pThis->szPrefix, pszLabel, pObjId->szObjId, pszPost); } /** * Display an object ID, without prefix and label * * @returns IPRT status code. * @param pThis The show exe instance data. * @param pObjId The object ID to display. * @param pszPost What to print after the ID (typically newline). */ static void HandleShowExeWorkerDisplayObjIdSimple(PSHOWEXEPKCS7 pThis, PCRTASN1OBJID pObjId, const char *pszPost) { int rc = RTAsn1QueryObjIdName(pObjId, pThis->szTmp, sizeof(pThis->szTmp)); if (RT_SUCCESS(rc)) { if (pThis->cVerbosity > 1) RTPrintf("%s (%s)%s", pThis->szTmp, pObjId->szObjId, pszPost); else RTPrintf("%s%s", pThis->szTmp, pszPost); } else RTPrintf("%s%s", pObjId->szObjId, pszPost); } /** * Display a signer info attribute. * * @returns IPRT status code. * @param pThis The show exe instance data. * @param offPrefix The current prefix offset. * @param pAttr The attribute to display. */ static int HandleShowExeWorkerPkcs7DisplayAttrib(PSHOWEXEPKCS7 pThis, size_t offPrefix, PCRTCRPKCS7ATTRIBUTE pAttr) { HandleShowExeWorkerDisplayObjId(pThis, &pAttr->Type, "", ":\n"); if (pThis->cVerbosity > 4 && pAttr->SeqCore.Asn1Core.uData.pu8) RTPrintf("%s uData.pu8=%p cb=%#x\n", pThis->szPrefix, pAttr->SeqCore.Asn1Core.uData.pu8, pAttr->SeqCore.Asn1Core.cb); int rc = VINF_SUCCESS; switch (pAttr->enmType) { case RTCRPKCS7ATTRIBUTETYPE_UNKNOWN: if (pAttr->uValues.pCores->cItems <= 1) RTPrintf("%s %u bytes\n", pThis->szPrefix,pAttr->uValues.pCores->SetCore.Asn1Core.cb); else RTPrintf("%s %u bytes divided by %u items\n", pThis->szPrefix, pAttr->uValues.pCores->SetCore.Asn1Core.cb, pAttr->uValues.pCores->cItems); break; /* Object IDs, use pObjIds. */ case RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS: if (pAttr->uValues.pObjIds->cItems != 1) RTPrintf("%s%u object IDs:", pThis->szPrefix, pAttr->uValues.pObjIds->cItems); for (unsigned i = 0; i < pAttr->uValues.pObjIds->cItems; i++) { if (pAttr->uValues.pObjIds->cItems == 1) RTPrintf("%s ", pThis->szPrefix); else RTPrintf("%s ObjId[%u]: ", pThis->szPrefix, i); HandleShowExeWorkerDisplayObjIdSimple(pThis, pAttr->uValues.pObjIds->papItems[i], "\n"); } break; /* Sequence of object IDs, use pObjIdSeqs. */ case RTCRPKCS7ATTRIBUTETYPE_MS_STATEMENT_TYPE: if (pAttr->uValues.pObjIdSeqs->cItems != 1) RTPrintf("%s%u object IDs:", pThis->szPrefix, pAttr->uValues.pObjIdSeqs->cItems); for (unsigned i = 0; i < pAttr->uValues.pObjIdSeqs->cItems; i++) { uint32_t const cObjIds = pAttr->uValues.pObjIdSeqs->papItems[i]->cItems; for (unsigned j = 0; j < cObjIds; j++) { if (pAttr->uValues.pObjIdSeqs->cItems == 1) RTPrintf("%s ", pThis->szPrefix); else RTPrintf("%s ObjIdSeq[%u]: ", pThis->szPrefix, i); if (cObjIds != 1) RTPrintf(" ObjId[%u]: ", j); HandleShowExeWorkerDisplayObjIdSimple(pThis, pAttr->uValues.pObjIdSeqs->papItems[i]->papItems[i], "\n"); } } break; /* Octet strings, use pOctetStrings. */ case RTCRPKCS7ATTRIBUTETYPE_OCTET_STRINGS: if (pAttr->uValues.pOctetStrings->cItems != 1) RTPrintf("%s%u octet strings:", pThis->szPrefix, pAttr->uValues.pOctetStrings->cItems); for (unsigned i = 0; i < pAttr->uValues.pOctetStrings->cItems; i++) { PCRTASN1OCTETSTRING pOctetString = pAttr->uValues.pOctetStrings->papItems[i]; uint32_t cbContent = pOctetString->Asn1Core.cb; if (cbContent > 0 && (cbContent <= 128 || pThis->cVerbosity >= 2)) { uint8_t const *pbContent = pOctetString->Asn1Core.uData.pu8; uint32_t off = 0; while (off < cbContent) { uint32_t cbNow = RT_MIN(cbContent - off, 16); if (pAttr->uValues.pOctetStrings->cItems == 1) RTPrintf("%s %#06x: %.*Rhxs\n", pThis->szPrefix, off, cbNow, &pbContent[off]); else RTPrintf("%s OctetString[%u]: %#06x: %.*Rhxs\n", pThis->szPrefix, i, off, cbNow, &pbContent[off]); off += cbNow; } } else RTPrintf("%s: OctetString[%u]: %u bytes\n", pThis->szPrefix, i, pOctetString->Asn1Core.cb); } break; /* Counter signatures (PKCS \#9), use pCounterSignatures. */ case RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES: RTPrintf("%s%u counter signatures, %u bytes in total\n", pThis->szPrefix, pAttr->uValues.pCounterSignatures->cItems, pAttr->uValues.pCounterSignatures->SetCore.Asn1Core.cb); for (uint32_t i = 0; i < pAttr->uValues.pCounterSignatures->cItems; i++) { size_t offPrefix2 = offPrefix; if (pAttr->uValues.pContentInfos->cItems > 1) offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, "CounterSig[%u]: ", i); else offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, " "); int rc2 = HandleShowExeWorkerPkcs7DisplaySignerInfo(pThis, offPrefix2, pAttr->uValues.pCounterSignatures->papItems[i]); if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) rc = rc2; } break; /* Signing time (PKCS \#9), use pSigningTime. */ case RTCRPKCS7ATTRIBUTETYPE_SIGNING_TIME: for (uint32_t i = 0; i < pAttr->uValues.pSigningTime->cItems; i++) { PCRTASN1TIME pTime = pAttr->uValues.pSigningTime->papItems[i]; char szTS[RTTIME_STR_LEN]; RTTimeToString(&pTime->Time, szTS, sizeof(szTS)); if (pAttr->uValues.pSigningTime->cItems == 1) RTPrintf("%s %s (%.*s)\n", pThis->szPrefix, szTS, pTime->Asn1Core.cb, pTime->Asn1Core.uData.pch); else RTPrintf("%s #%u: %s (%.*s)\n", pThis->szPrefix, i, szTS, pTime->Asn1Core.cb, pTime->Asn1Core.uData.pch); } break; /* Microsoft timestamp info (RFC-3161) signed data, use pContentInfo. */ case RTCRPKCS7ATTRIBUTETYPE_MS_TIMESTAMP: case RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE: if (pAttr->uValues.pContentInfos->cItems > 1) RTPrintf("%s%u nested signatures, %u bytes in total\n", pThis->szPrefix, pAttr->uValues.pContentInfos->cItems, pAttr->uValues.pContentInfos->SetCore.Asn1Core.cb); for (unsigned i = 0; i < pAttr->uValues.pContentInfos->cItems; i++) { size_t offPrefix2 = offPrefix; if (pAttr->uValues.pContentInfos->cItems > 1) offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, "NestedSig[%u]: ", i); else offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, " "); // offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, "NestedSig: ", i); PCRTCRPKCS7CONTENTINFO pContentInfo = pAttr->uValues.pContentInfos->papItems[i]; int rc2; if (RTCrPkcs7ContentInfo_IsSignedData(pContentInfo)) rc2 = HandleShowExeWorkerPkcs7Display(pThis, pContentInfo->u.pSignedData, offPrefix2, pContentInfo); else rc2 = RTMsgErrorRc(VERR_ASN1_UNEXPECTED_OBJ_ID, "%sPKCS#7 content in nested signature is not 'signedData': %s", pThis->szPrefix, pContentInfo->ContentType.szObjId); if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) rc = rc2; } break; case RTCRPKCS7ATTRIBUTETYPE_APPLE_MULTI_CD_PLIST: if (pAttr->uValues.pContentInfos->cItems != 1) RTPrintf("%s%u plists, expected only 1.\n", pThis->szPrefix, pAttr->uValues.pOctetStrings->cItems); for (unsigned i = 0; i < pAttr->uValues.pOctetStrings->cItems; i++) { PCRTASN1OCTETSTRING pOctetString = pAttr->uValues.pOctetStrings->papItems[i]; size_t cbContent = pOctetString->Asn1Core.cb; char const *pchContent = pOctetString->Asn1Core.uData.pch; rc = RTStrValidateEncodingEx(pchContent, cbContent, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH); if (RT_SUCCESS(rc)) { while (cbContent > 0) { const char *pchNewLine = (const char *)memchr(pchContent, '\n', cbContent); size_t cchToWrite = pchNewLine ? pchNewLine - pchContent : cbContent; if (pAttr->uValues.pOctetStrings->cItems == 1) RTPrintf("%s %.*s\n", pThis->szPrefix, cchToWrite, pchContent); else RTPrintf("%s plist[%u]: %.*s\n", pThis->szPrefix, i, cchToWrite, pchContent); if (!pchNewLine) break; pchContent = pchNewLine + 1; cbContent -= cchToWrite + 1; } } else { if (pAttr->uValues.pContentInfos->cItems != 1) RTPrintf("%s: plist[%u]: Invalid UTF-8: %Rrc\n", pThis->szPrefix, i, rc); else RTPrintf("%s: Invalid UTF-8: %Rrc\n", pThis->szPrefix, rc); for (uint32_t off = 0; off < cbContent; off += 16) { size_t cbNow = RT_MIN(cbContent - off, 16); if (pAttr->uValues.pOctetStrings->cItems == 1) RTPrintf("%s %#06x: %.*Rhxs\n", pThis->szPrefix, off, cbNow, &pchContent[off]); else RTPrintf("%s plist[%u]: %#06x: %.*Rhxs\n", pThis->szPrefix, i, off, cbNow, &pchContent[off]); } } } break; case RTCRPKCS7ATTRIBUTETYPE_INVALID: RTPrintf("%sINVALID!\n", pThis->szPrefix); break; case RTCRPKCS7ATTRIBUTETYPE_NOT_PRESENT: RTPrintf("%sNOT PRESENT!\n", pThis->szPrefix); break; default: RTPrintf("%senmType=%d!\n", pThis->szPrefix, pAttr->enmType); break; } return rc; } /** * Displays a SignerInfo structure. * * @returns IPRT status code. * @param pThis The show exe instance data. * @param offPrefix The current prefix offset. * @param pSignerInfo The structure to display. */ static int HandleShowExeWorkerPkcs7DisplaySignerInfo(PSHOWEXEPKCS7 pThis, size_t offPrefix, PCRTCRPKCS7SIGNERINFO pSignerInfo) { int rc = RTAsn1Integer_ToString(&pSignerInfo->IssuerAndSerialNumber.SerialNumber, pThis->szTmp, sizeof(pThis->szTmp), 0 /*fFlags*/, NULL); if (RT_FAILURE(rc)) RTStrPrintf(pThis->szTmp, sizeof(pThis->szTmp), "%Rrc", rc); RTPrintf("%s Serial No: %s\n", pThis->szPrefix, pThis->szTmp); rc = RTCrX509Name_FormatAsString(&pSignerInfo->IssuerAndSerialNumber.Name, pThis->szTmp, sizeof(pThis->szTmp), NULL); if (RT_FAILURE(rc)) RTStrPrintf(pThis->szTmp, sizeof(pThis->szTmp), "%Rrc", rc); RTPrintf("%s Issuer: %s\n", pThis->szPrefix, pThis->szTmp); const char *pszType = RTCrDigestTypeToName(RTCrX509AlgorithmIdentifier_GetDigestType(&pSignerInfo->DigestAlgorithm, true /*fPureDigestsOnly*/)); if (!pszType) pszType = pSignerInfo->DigestAlgorithm.Algorithm.szObjId; RTPrintf("%s Digest Algorithm: %s", pThis->szPrefix, pszType); if (pThis->cVerbosity > 1) RTPrintf(" (%s)\n", pSignerInfo->DigestAlgorithm.Algorithm.szObjId); else RTPrintf("\n"); HandleShowExeWorkerDisplayObjId(pThis, &pSignerInfo->DigestEncryptionAlgorithm.Algorithm, "Digest Encryption Algorithm: ", "\n"); if (pSignerInfo->AuthenticatedAttributes.cItems == 0) RTPrintf("%s Authenticated Attributes: none\n", pThis->szPrefix); else { RTPrintf("%s Authenticated Attributes: %u item%s\n", pThis->szPrefix, pSignerInfo->AuthenticatedAttributes.cItems, pSignerInfo->AuthenticatedAttributes.cItems > 1 ? "s" : ""); for (unsigned j = 0; j < pSignerInfo->AuthenticatedAttributes.cItems; j++) { PRTCRPKCS7ATTRIBUTE pAttr = pSignerInfo->AuthenticatedAttributes.papItems[j]; size_t offPrefix3 = offPrefix+ RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, " AuthAttrib[%u]: ", j); HandleShowExeWorkerPkcs7DisplayAttrib(pThis, offPrefix3, pAttr); } pThis->szPrefix[offPrefix] = '\0'; } if (pSignerInfo->UnauthenticatedAttributes.cItems == 0) RTPrintf("%s Unauthenticated Attributes: none\n", pThis->szPrefix); else { RTPrintf("%s Unauthenticated Attributes: %u item%s\n", pThis->szPrefix, pSignerInfo->UnauthenticatedAttributes.cItems, pSignerInfo->UnauthenticatedAttributes.cItems > 1 ? "s" : ""); for (unsigned j = 0; j < pSignerInfo->UnauthenticatedAttributes.cItems; j++) { PRTCRPKCS7ATTRIBUTE pAttr = pSignerInfo->UnauthenticatedAttributes.papItems[j]; size_t offPrefix3 = offPrefix + RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, " UnauthAttrib[%u]: ", j); HandleShowExeWorkerPkcs7DisplayAttrib(pThis, offPrefix3, pAttr); } pThis->szPrefix[offPrefix] = '\0'; } /** @todo show the encrypted stuff (EncryptedDigest)? */ return rc; } /** * Displays a Microsoft SPC indirect data structure. * * @returns IPRT status code. * @param pThis The show exe instance data. * @param offPrefix The current prefix offset. * @param pIndData The indirect data to display. */ static int HandleShowExeWorkerPkcs7DisplaySpcIdirectDataContent(PSHOWEXEPKCS7 pThis, size_t offPrefix, PCRTCRSPCINDIRECTDATACONTENT pIndData) { /* * The image hash. */ RTDIGESTTYPE const enmDigestType = RTCrX509AlgorithmIdentifier_GetDigestType(&pIndData->DigestInfo.DigestAlgorithm, true /*fPureDigestsOnly*/); const char *pszDigestType = RTCrDigestTypeToName(enmDigestType); RTPrintf("%s Digest Type: %s", pThis->szPrefix, pszDigestType); if (pThis->cVerbosity > 1) RTPrintf(" (%s)\n", pIndData->DigestInfo.DigestAlgorithm.Algorithm.szObjId); else RTPrintf("\n"); RTPrintf("%s Digest: %.*Rhxs\n", pThis->szPrefix, pIndData->DigestInfo.Digest.Asn1Core.cb, pIndData->DigestInfo.Digest.Asn1Core.uData.pu8); /* * The data/file/url. */ switch (pIndData->Data.enmType) { case RTCRSPCAAOVTYPE_PE_IMAGE_DATA: { RTPrintf("%s Data Type: PE Image Data\n", pThis->szPrefix); PRTCRSPCPEIMAGEDATA pPeImage = pIndData->Data.uValue.pPeImage; /** @todo display "Flags". */ switch (pPeImage->T0.File.enmChoice) { case RTCRSPCLINKCHOICE_MONIKER: { PRTCRSPCSERIALIZEDOBJECT pMoniker = pPeImage->T0.File.u.pMoniker; if (RTCrSpcSerializedObject_IsPresent(pMoniker)) { if (RTUuidCompareStr(pMoniker->Uuid.Asn1Core.uData.pUuid, RTCRSPCSERIALIZEDOBJECT_UUID_STR) == 0) { RTPrintf("%s Moniker: SpcSerializedObject (%RTuuid)\n", pThis->szPrefix, pMoniker->Uuid.Asn1Core.uData.pUuid); PCRTCRSPCSERIALIZEDOBJECTATTRIBUTES pData = pMoniker->u.pData; if (pData) for (uint32_t i = 0; i < pData->cItems; i++) { RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, "MonikerAttrib[%u]: ", i); switch (pData->papItems[i]->enmType) { case RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V2: case RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1: { PCRTCRSPCSERIALIZEDPAGEHASHES pPgHashes = pData->papItems[i]->u.pPageHashes; uint32_t const cbHash = pData->papItems[i]->enmType == RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1 ? 160/8 /*SHA-1*/ : 256/8 /*SHA-256*/; uint32_t const cPages = pPgHashes->RawData.Asn1Core.cb / (cbHash + sizeof(uint32_t)); RTPrintf("%sPage Hashes version %u - %u pages (%u bytes total)\n", pThis->szPrefix, pData->papItems[i]->enmType == RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1 ? 1 : 2, cPages, pPgHashes->RawData.Asn1Core.cb); if (pThis->cVerbosity > 0) { PCRTCRSPCPEIMAGEPAGEHASHES pPg = pPgHashes->pData; for (unsigned iPg = 0; iPg < cPages; iPg++) { uint32_t offHash = 0; do { if (offHash == 0) RTPrintf("%.*s Page#%04u/%#08x: ", offPrefix, pThis->szPrefix, iPg, pPg->Generic.offFile); else RTPrintf("%.*s ", offPrefix, pThis->szPrefix); uint32_t cbLeft = cbHash - offHash; if (cbLeft > 24) cbLeft = 16; RTPrintf("%.*Rhxs\n", cbLeft, &pPg->Generic.abHash[offHash]); offHash += cbLeft; } while (offHash < cbHash); pPg = (PCRTCRSPCPEIMAGEPAGEHASHES)&pPg->Generic.abHash[cbHash]; } if (pThis->cVerbosity > 3) RTPrintf("%.*Rhxd\n", pPgHashes->RawData.Asn1Core.cb, pPgHashes->RawData.Asn1Core.uData.pu8); } break; } case RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_UNKNOWN: HandleShowExeWorkerDisplayObjIdSimple(pThis, &pData->papItems[i]->Type, "\n"); break; case RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_NOT_PRESENT: RTPrintf("%sNot present!\n", pThis->szPrefix); break; default: RTPrintf("%senmType=%d!\n", pThis->szPrefix, pData->papItems[i]->enmType); break; } pThis->szPrefix[offPrefix] = '\0'; } else RTPrintf("%s pData is NULL!\n", pThis->szPrefix); } else RTPrintf("%s Moniker: Unknown UUID: %RTuuid\n", pThis->szPrefix, pMoniker->Uuid.Asn1Core.uData.pUuid); } else RTPrintf("%s Moniker: not present\n", pThis->szPrefix); break; } case RTCRSPCLINKCHOICE_URL: { const char *pszUrl = NULL; int rc = pPeImage->T0.File.u.pUrl ? RTAsn1String_QueryUtf8(pPeImage->T0.File.u.pUrl, &pszUrl, NULL) : VERR_NOT_FOUND; if (RT_SUCCESS(rc)) RTPrintf("%s URL: '%s'\n", pThis->szPrefix, pszUrl); else RTPrintf("%s URL: rc=%Rrc\n", pThis->szPrefix, rc); break; } case RTCRSPCLINKCHOICE_FILE: { const char *pszFile = NULL; int rc = pPeImage->T0.File.u.pT2 && pPeImage->T0.File.u.pT2->File.u.pAscii ? RTAsn1String_QueryUtf8(pPeImage->T0.File.u.pT2->File.u.pAscii, &pszFile, NULL) : VERR_NOT_FOUND; if (RT_SUCCESS(rc)) RTPrintf("%s File: '%s'\n", pThis->szPrefix, pszFile); else RTPrintf("%s File: rc=%Rrc\n", pThis->szPrefix, rc); if (pThis->cVerbosity > 4 && pPeImage->T0.File.u.pT2 == NULL) RTPrintf("%s pT2=NULL\n", pThis->szPrefix); else if (pThis->cVerbosity > 4) { PCRTASN1STRING pStr = pPeImage->T0.File.u.pT2->File.u.pAscii; RTPrintf("%s pT2=%p/%p LB %#x fFlags=%#x pOps=%p (%s)\n" "%s enmChoice=%d pStr=%p/%p LB %#x fFlags=%#x\n", pThis->szPrefix, pPeImage->T0.File.u.pT2, pPeImage->T0.File.u.pT2->CtxTag2.Asn1Core.uData.pu8, pPeImage->T0.File.u.pT2->CtxTag2.Asn1Core.cb, pPeImage->T0.File.u.pT2->CtxTag2.Asn1Core.fFlags, pPeImage->T0.File.u.pT2->CtxTag2.Asn1Core.pOps, pPeImage->T0.File.u.pT2->CtxTag2.Asn1Core.pOps ? pPeImage->T0.File.u.pT2->CtxTag2.Asn1Core.pOps->pszName : "", pThis->szPrefix, pPeImage->T0.File.u.pT2->File.enmChoice, pStr, pStr ? pStr->Asn1Core.uData.pu8 : NULL, pStr ? pStr->Asn1Core.cb : 0, pStr ? pStr->Asn1Core.fFlags : 0); } break; } case RTCRSPCLINKCHOICE_NOT_PRESENT: RTPrintf("%s File not present!\n", pThis->szPrefix); break; default: RTPrintf("%s enmChoice=%d!\n", pThis->szPrefix, pPeImage->T0.File.enmChoice); break; } break; } case RTCRSPCAAOVTYPE_UNKNOWN: HandleShowExeWorkerDisplayObjId(pThis, &pIndData->Data.Type, " Data Type: ", "\n"); break; case RTCRSPCAAOVTYPE_NOT_PRESENT: RTPrintf("%s Data Type: Not present!\n", pThis->szPrefix); break; default: RTPrintf("%s Data Type: enmType=%d!\n", pThis->szPrefix, pIndData->Data.enmType); break; } return VINF_SUCCESS; } /** * Display an PKCS#7 signed data instance. * * @returns IPRT status code. * @param pThis The show exe instance data. * @param pSignedData The signed data to display. * @param offPrefix The current prefix offset. * @param pContentInfo The content info structure (for the size). */ static int HandleShowExeWorkerPkcs7Display(PSHOWEXEPKCS7 pThis, PRTCRPKCS7SIGNEDDATA pSignedData, size_t offPrefix, PCRTCRPKCS7CONTENTINFO pContentInfo) { pThis->szPrefix[offPrefix] = '\0'; RTPrintf("%sPKCS#7 signature: %u (%#x) bytes\n", pThis->szPrefix, RTASN1CORE_GET_RAW_ASN1_SIZE(&pContentInfo->SeqCore.Asn1Core), RTASN1CORE_GET_RAW_ASN1_SIZE(&pContentInfo->SeqCore.Asn1Core)); /* * Display list of signing algorithms. */ RTPrintf("%sDigestAlgorithms: ", pThis->szPrefix); if (pSignedData->DigestAlgorithms.cItems == 0) RTPrintf("none"); for (unsigned i = 0; i < pSignedData->DigestAlgorithms.cItems; i++) { PCRTCRX509ALGORITHMIDENTIFIER pAlgoId = pSignedData->DigestAlgorithms.papItems[i]; const char *pszDigestType = RTCrDigestTypeToName(RTCrX509AlgorithmIdentifier_GetDigestType(pAlgoId, true /*fPureDigestsOnly*/)); if (!pszDigestType) pszDigestType = pAlgoId->Algorithm.szObjId; RTPrintf(i == 0 ? "%s" : ", %s", pszDigestType); if (pThis->cVerbosity > 1) RTPrintf(" (%s)", pAlgoId->Algorithm.szObjId); } RTPrintf("\n"); /* * Display the signed data content. */ if (RTAsn1ObjId_CompareWithString(&pSignedData->ContentInfo.ContentType, RTCRSPCINDIRECTDATACONTENT_OID) == 0) { RTPrintf("%s ContentType: SpcIndirectDataContent (" RTCRSPCINDIRECTDATACONTENT_OID ")\n", pThis->szPrefix); size_t offPrefix2 = RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, " SPC Ind Data: "); HandleShowExeWorkerPkcs7DisplaySpcIdirectDataContent(pThis, offPrefix2 + offPrefix, pSignedData->ContentInfo.u.pIndirectDataContent); pThis->szPrefix[offPrefix] = '\0'; } else { HandleShowExeWorkerDisplayObjId(pThis, &pSignedData->ContentInfo.ContentType, " ContentType: ", " - not implemented.\n"); RTPrintf("%s %u (%#x) bytes\n", pThis->szPrefix, pSignedData->ContentInfo.Content.Asn1Core.cb, pSignedData->ContentInfo.Content.Asn1Core.cb); } /* * Display certificates (Certificates). */ if (pSignedData->Certificates.cItems > 0) { RTPrintf("%s Certificates: %u\n", pThis->szPrefix, pSignedData->Certificates.cItems); for (uint32_t i = 0; i < pSignedData->Certificates.cItems; i++) { PCRTCRPKCS7CERT pCert = pSignedData->Certificates.papItems[i]; if (i != 0 && pThis->cVerbosity >= 2) RTPrintf("\n"); switch (pCert->enmChoice) { case RTCRPKCS7CERTCHOICE_X509: { PCRTCRX509CERTIFICATE pX509Cert = pCert->u.pX509Cert; int rc2 = RTAsn1QueryObjIdName(&pX509Cert->SignatureAlgorithm.Algorithm, pThis->szTmp, sizeof(pThis->szTmp)); RTPrintf("%s Certificate #%u: %s\n", pThis->szPrefix, i, RT_SUCCESS(rc2) ? pThis->szTmp : pX509Cert->SignatureAlgorithm.Algorithm.szObjId); rc2 = RTCrX509Name_FormatAsString(&pX509Cert->TbsCertificate.Subject, pThis->szTmp, sizeof(pThis->szTmp), NULL); if (RT_FAILURE(rc2)) RTStrPrintf(pThis->szTmp, sizeof(pThis->szTmp), "%Rrc", rc2); RTPrintf("%s Subject: %s\n", pThis->szPrefix, pThis->szTmp); rc2 = RTCrX509Name_FormatAsString(&pX509Cert->TbsCertificate.Issuer, pThis->szTmp, sizeof(pThis->szTmp), NULL); if (RT_FAILURE(rc2)) RTStrPrintf(pThis->szTmp, sizeof(pThis->szTmp), "%Rrc", rc2); RTPrintf("%s Issuer: %s\n", pThis->szPrefix, pThis->szTmp); char szNotAfter[RTTIME_STR_LEN]; RTPrintf("%s Valid: %s thru %s\n", pThis->szPrefix, RTTimeToString(&pX509Cert->TbsCertificate.Validity.NotBefore.Time, pThis->szTmp, sizeof(pThis->szTmp)), RTTimeToString(&pX509Cert->TbsCertificate.Validity.NotAfter.Time, szNotAfter, sizeof(szNotAfter))); break; } default: RTPrintf("%s Certificate #%u: Unsupported type\n", pThis->szPrefix, i); break; } if (pThis->cVerbosity >= 2) RTAsn1Dump(RTCrPkcs7Cert_GetAsn1Core(pSignedData->Certificates.papItems[i]), 0, ((uint32_t)offPrefix + 9) / 2, RTStrmDumpPrintfV, g_pStdOut); } /** @todo display certificates properly. */ } if (pSignedData->Crls.cb > 0) RTPrintf("%s CRLs: %u bytes\n", pThis->szPrefix, pSignedData->Crls.cb); /* * Show signatures (SignerInfos). */ unsigned const cSigInfos = pSignedData->SignerInfos.cItems; if (cSigInfos != 1) RTPrintf("%s SignerInfos: %u signers\n", pThis->szPrefix, cSigInfos); else RTPrintf("%s SignerInfos:\n", pThis->szPrefix); int rc = VINF_SUCCESS; for (unsigned i = 0; i < cSigInfos; i++) { size_t offPrefix2 = offPrefix; if (cSigInfos != 1) offPrefix2 += RTStrPrintf(&pThis->szPrefix[offPrefix], sizeof(pThis->szPrefix) - offPrefix, "SignerInfo[%u]: ", i); int rc2 = HandleShowExeWorkerPkcs7DisplaySignerInfo(pThis, offPrefix2, pSignedData->SignerInfos.papItems[i]); if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) rc = rc2; } pThis->szPrefix[offPrefix] = '\0'; return rc; } /* * The 'show-exe' command. */ static RTEXITCODE HelpShowExe(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "show-exe [--verbose|-v] [--quiet|-q] [exe2 [..]]\n"); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleShowExe(int cArgs, char **papszArgs) { /* * Parse arguments. */ static const RTGETOPTDEF s_aOptions[] = { { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, }; unsigned cVerbosity = 0; RTLDRARCH enmLdrArch = RTLDRARCH_WHATEVER; RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); AssertRCReturn(rc, RTEXITCODE_FAILURE); RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion)) && ch != VINF_GETOPT_NOT_OPTION) { switch (ch) { case 'v': cVerbosity++; break; case 'q': cVerbosity = 0; break; case 'V': return HandleVersion(cArgs, papszArgs); case 'h': return HelpShowExe(g_pStdOut, RTSIGNTOOLHELP_FULL); default: return RTGetOptPrintError(ch, &ValueUnion); } } if (ch != VINF_GETOPT_NOT_OPTION) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given."); /* * Do it. */ unsigned iFile = 0; RTEXITCODE rcExit = RTEXITCODE_SUCCESS; do { RTPrintf(iFile == 0 ? "%s:\n" : "\n%s:\n", ValueUnion.psz); SHOWEXEPKCS7 This; RT_ZERO(This); This.cVerbosity = cVerbosity; RTEXITCODE rcExitThis = SignToolPkcs7Exe_InitFromFile(&This, ValueUnion.psz, cVerbosity, enmLdrArch); if (rcExitThis == RTEXITCODE_SUCCESS) { rc = HandleShowExeWorkerPkcs7Display(&This, This.pSignedData, 0, &This.ContentInfo); if (RT_FAILURE(rc)) rcExit = RTEXITCODE_FAILURE; SignToolPkcs7Exe_Delete(&This); } if (rcExitThis != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS) rcExit = rcExitThis; iFile++; } while ((ch = RTGetOpt(&GetState, &ValueUnion)) == VINF_GETOPT_NOT_OPTION); if (ch != 0) return RTGetOptPrintError(ch, &ValueUnion); return rcExit; } /* * The 'show-cat' command. */ static RTEXITCODE HelpShowCat(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "show-cat [--verbose|-v] [--quiet|-q] [cat2 [..]]\n"); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleShowCat(int cArgs, char **papszArgs) { /* * Parse arguments. */ static const RTGETOPTDEF s_aOptions[] = { { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, }; unsigned cVerbosity = 0; RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); AssertRCReturn(rc, RTEXITCODE_FAILURE); RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion)) && ch != VINF_GETOPT_NOT_OPTION) { switch (ch) { case 'v': cVerbosity++; break; case 'q': cVerbosity = 0; break; case 'V': return HandleVersion(cArgs, papszArgs); case 'h': return HelpShowCat(g_pStdOut, RTSIGNTOOLHELP_FULL); default: return RTGetOptPrintError(ch, &ValueUnion); } } if (ch != VINF_GETOPT_NOT_OPTION) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given."); /* * Do it. */ unsigned iFile = 0; RTEXITCODE rcExit = RTEXITCODE_SUCCESS; do { RTPrintf(iFile == 0 ? "%s:\n" : "\n%s:\n", ValueUnion.psz); SHOWEXEPKCS7 This; RT_ZERO(This); This.cVerbosity = cVerbosity; RTEXITCODE rcExitThis = SignToolPkcs7_InitFromFile(&This, ValueUnion.psz, cVerbosity); if (rcExitThis == RTEXITCODE_SUCCESS) { This.hLdrMod = NIL_RTLDRMOD; rc = HandleShowExeWorkerPkcs7Display(&This, This.pSignedData, 0, &This.ContentInfo); if (RT_FAILURE(rc)) rcExit = RTEXITCODE_FAILURE; SignToolPkcs7Exe_Delete(&This); } if (rcExitThis != RTEXITCODE_SUCCESS && rcExit == RTEXITCODE_SUCCESS) rcExit = rcExitThis; iFile++; } while ((ch = RTGetOpt(&GetState, &ValueUnion)) == VINF_GETOPT_NOT_OPTION); if (ch != 0) return RTGetOptPrintError(ch, &ValueUnion); return rcExit; } /********************************************************************************************************************************* * The 'hash-exe' command. * *********************************************************************************************************************************/ static RTEXITCODE HelpHashExe(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "hash-exe [--verbose|-v] [--quiet|-q] [exe2 [..]]\n"); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleHashExe(int cArgs, char **papszArgs) { /* * Parse arguments. */ static const RTGETOPTDEF s_aOptions[] = { { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, }; unsigned cVerbosity = 0; RTLDRARCH enmLdrArch = RTLDRARCH_WHATEVER; RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); AssertRCReturn(rc, RTEXITCODE_FAILURE); RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion)) && ch != VINF_GETOPT_NOT_OPTION) { switch (ch) { case 'v': cVerbosity++; break; case 'q': cVerbosity = 0; break; case 'V': return HandleVersion(cArgs, papszArgs); case 'h': return HelpHashExe(g_pStdOut, RTSIGNTOOLHELP_FULL); default: return RTGetOptPrintError(ch, &ValueUnion); } } if (ch != VINF_GETOPT_NOT_OPTION) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No executable given."); /* * Do it. */ unsigned iFile = 0; RTEXITCODE rcExit = RTEXITCODE_SUCCESS; do { RTPrintf(iFile == 0 ? "%s:\n" : "\n%s:\n", ValueUnion.psz); RTERRINFOSTATIC ErrInfo; RTLDRMOD hLdrMod; rc = RTLdrOpenEx(ValueUnion.psz, RTLDR_O_FOR_VALIDATION, enmLdrArch, &hLdrMod, RTErrInfoInitStatic(&ErrInfo)); if (RT_SUCCESS(rc)) { uint8_t abHash[RTSHA512_HASH_SIZE]; char szDigest[RTSHA512_DIGEST_LEN + 1]; /* SHA-1: */ rc = RTLdrHashImage(hLdrMod, RTDIGESTTYPE_SHA1, abHash, sizeof(abHash)); if (RT_SUCCESS(rc)) RTSha1ToString(abHash, szDigest, sizeof(szDigest)); else RTStrPrintf(szDigest, sizeof(szDigest), "%Rrc", rc); RTPrintf(" SHA-1: %s\n", szDigest); /* SHA-256: */ rc = RTLdrHashImage(hLdrMod, RTDIGESTTYPE_SHA256, abHash, sizeof(abHash)); if (RT_SUCCESS(rc)) RTSha256ToString(abHash, szDigest, sizeof(szDigest)); else RTStrPrintf(szDigest, sizeof(szDigest), "%Rrc", rc); RTPrintf(" SHA-256: %s\n", szDigest); /* SHA-512: */ rc = RTLdrHashImage(hLdrMod, RTDIGESTTYPE_SHA512, abHash, sizeof(abHash)); if (RT_SUCCESS(rc)) RTSha512ToString(abHash, szDigest, sizeof(szDigest)); else RTStrPrintf(szDigest, sizeof(szDigest), "%Rrc", rc); RTPrintf(" SHA-512: %s\n", szDigest); RTLdrClose(hLdrMod); } else rcExit = RTMsgErrorExitFailure("Failed to open '%s': %Rrc%#RTeim", ValueUnion.psz, rc, &ErrInfo.Core); } while ((ch = RTGetOpt(&GetState, &ValueUnion)) == VINF_GETOPT_NOT_OPTION); if (ch != 0) return RTGetOptPrintError(ch, &ValueUnion); return rcExit; } /********************************************************************************************************************************* * The 'make-tainfo' command. * *********************************************************************************************************************************/ static RTEXITCODE HelpMakeTaInfo(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, "make-tainfo [--verbose|--quiet] [--cert ] [-o|--output] \n"); return RTEXITCODE_SUCCESS; } typedef struct MAKETAINFOSTATE { int cVerbose; const char *pszCert; const char *pszOutput; } MAKETAINFOSTATE; /** @callback_method_impl{FNRTASN1ENCODEWRITER} */ static DECLCALLBACK(int) handleMakeTaInfoWriter(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo) { RT_NOREF_PV(pErrInfo); return RTStrmWrite((PRTSTREAM)pvUser, pvBuf, cbToWrite); } static RTEXITCODE HandleMakeTaInfo(int cArgs, char **papszArgs) { /* * Parse arguments. */ static const RTGETOPTDEF s_aOptions[] = { { "--cert", 'c', RTGETOPT_REQ_STRING }, { "--output", 'o', RTGETOPT_REQ_STRING }, { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, { "--quiet", 'q', RTGETOPT_REQ_NOTHING }, }; MAKETAINFOSTATE State = { 0, NULL, NULL }; RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); AssertRCReturn(rc, RTEXITCODE_FAILURE); RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) { switch (ch) { case 'c': if (State.pszCert) return RTMsgErrorExit(RTEXITCODE_FAILURE, "The --cert option can only be used once."); State.pszCert = ValueUnion.psz; break; case 'o': case VINF_GETOPT_NOT_OPTION: if (State.pszOutput) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Multiple output files specified."); State.pszOutput = ValueUnion.psz; break; case 'v': State.cVerbose++; break; case 'q': State.cVerbose = 0; break; case 'V': return HandleVersion(cArgs, papszArgs); case 'h': return HelpMakeTaInfo(g_pStdOut, RTSIGNTOOLHELP_FULL); default: return RTGetOptPrintError(ch, &ValueUnion); } } if (!State.pszCert) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No input certificate was specified."); if (!State.pszOutput) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No output file was specified."); /* * Read the certificate. */ RTERRINFOSTATIC StaticErrInfo; RTCRX509CERTIFICATE Certificate; rc = RTCrX509Certificate_ReadFromFile(&Certificate, State.pszCert, 0, &g_RTAsn1DefaultAllocator, RTErrInfoInitStatic(&StaticErrInfo)); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error reading certificate from %s: %Rrc - %s", State.pszCert, rc, StaticErrInfo.szMsg); /* * Construct the trust anchor information. */ RTCRTAFTRUSTANCHORINFO TrustAnchor; rc = RTCrTafTrustAnchorInfo_Init(&TrustAnchor, &g_RTAsn1DefaultAllocator); if (RT_SUCCESS(rc)) { /* Public key. */ Assert(RTCrX509SubjectPublicKeyInfo_IsPresent(&TrustAnchor.PubKey)); RTCrX509SubjectPublicKeyInfo_Delete(&TrustAnchor.PubKey); rc = RTCrX509SubjectPublicKeyInfo_Clone(&TrustAnchor.PubKey, &Certificate.TbsCertificate.SubjectPublicKeyInfo, &g_RTAsn1DefaultAllocator); if (RT_FAILURE(rc)) RTMsgError("RTCrX509SubjectPublicKeyInfo_Clone failed: %Rrc", rc); RTAsn1Core_ResetImplict(RTCrX509SubjectPublicKeyInfo_GetAsn1Core(&TrustAnchor.PubKey)); /* temporary hack. */ /* Key Identifier. */ PCRTASN1OCTETSTRING pKeyIdentifier = NULL; if (Certificate.TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_SUBJECT_KEY_IDENTIFIER) pKeyIdentifier = Certificate.TbsCertificate.T3.pSubjectKeyIdentifier; else if ( (Certificate.TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_AUTHORITY_KEY_IDENTIFIER) && RTCrX509Certificate_IsSelfSigned(&Certificate) && RTAsn1OctetString_IsPresent(&Certificate.TbsCertificate.T3.pAuthorityKeyIdentifier->KeyIdentifier) ) pKeyIdentifier = &Certificate.TbsCertificate.T3.pAuthorityKeyIdentifier->KeyIdentifier; else if ( (Certificate.TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_OLD_AUTHORITY_KEY_IDENTIFIER) && RTCrX509Certificate_IsSelfSigned(&Certificate) && RTAsn1OctetString_IsPresent(&Certificate.TbsCertificate.T3.pOldAuthorityKeyIdentifier->KeyIdentifier) ) pKeyIdentifier = &Certificate.TbsCertificate.T3.pOldAuthorityKeyIdentifier->KeyIdentifier; if (pKeyIdentifier && pKeyIdentifier->Asn1Core.cb > 0) { Assert(RTAsn1OctetString_IsPresent(&TrustAnchor.KeyIdentifier)); RTAsn1OctetString_Delete(&TrustAnchor.KeyIdentifier); rc = RTAsn1OctetString_Clone(&TrustAnchor.KeyIdentifier, pKeyIdentifier, &g_RTAsn1DefaultAllocator); if (RT_FAILURE(rc)) RTMsgError("RTAsn1OctetString_Clone failed: %Rrc", rc); RTAsn1Core_ResetImplict(RTAsn1OctetString_GetAsn1Core(&TrustAnchor.KeyIdentifier)); /* temporary hack. */ } else RTMsgWarning("No key identifier found or has zero length."); /* Subject */ if (RT_SUCCESS(rc)) { Assert(!RTCrTafCertPathControls_IsPresent(&TrustAnchor.CertPath)); rc = RTCrTafCertPathControls_Init(&TrustAnchor.CertPath, &g_RTAsn1DefaultAllocator); if (RT_SUCCESS(rc)) { Assert(RTCrX509Name_IsPresent(&TrustAnchor.CertPath.TaName)); RTCrX509Name_Delete(&TrustAnchor.CertPath.TaName); rc = RTCrX509Name_Clone(&TrustAnchor.CertPath.TaName, &Certificate.TbsCertificate.Subject, &g_RTAsn1DefaultAllocator); if (RT_SUCCESS(rc)) { RTAsn1Core_ResetImplict(RTCrX509Name_GetAsn1Core(&TrustAnchor.CertPath.TaName)); /* temporary hack. */ rc = RTCrX509Name_RecodeAsUtf8(&TrustAnchor.CertPath.TaName, &g_RTAsn1DefaultAllocator); if (RT_FAILURE(rc)) RTMsgError("RTCrX509Name_RecodeAsUtf8 failed: %Rrc", rc); } else RTMsgError("RTCrX509Name_Clone failed: %Rrc", rc); } else RTMsgError("RTCrTafCertPathControls_Init failed: %Rrc", rc); } /* Check that what we've constructed makes some sense. */ if (RT_SUCCESS(rc)) { rc = RTCrTafTrustAnchorInfo_CheckSanity(&TrustAnchor, 0, RTErrInfoInitStatic(&StaticErrInfo), "TAI"); if (RT_FAILURE(rc)) RTMsgError("RTCrTafTrustAnchorInfo_CheckSanity failed: %Rrc - %s", rc, StaticErrInfo.szMsg); } if (RT_SUCCESS(rc)) { /* * Encode it and write it to the output file. */ uint32_t cbEncoded; rc = RTAsn1EncodePrepare(RTCrTafTrustAnchorInfo_GetAsn1Core(&TrustAnchor), RTASN1ENCODE_F_DER, &cbEncoded, RTErrInfoInitStatic(&StaticErrInfo)); if (RT_SUCCESS(rc)) { if (State.cVerbose >= 1) RTAsn1Dump(RTCrTafTrustAnchorInfo_GetAsn1Core(&TrustAnchor), 0, 0, RTStrmDumpPrintfV, g_pStdOut); PRTSTREAM pStrm; rc = RTStrmOpen(State.pszOutput, "wb", &pStrm); if (RT_SUCCESS(rc)) { rc = RTAsn1EncodeWrite(RTCrTafTrustAnchorInfo_GetAsn1Core(&TrustAnchor), RTASN1ENCODE_F_DER, handleMakeTaInfoWriter, pStrm, RTErrInfoInitStatic(&StaticErrInfo)); if (RT_SUCCESS(rc)) { rc = RTStrmClose(pStrm); if (RT_SUCCESS(rc)) RTMsgInfo("Successfully wrote TrustedAnchorInfo to '%s'.", State.pszOutput); else RTMsgError("RTStrmClose failed: %Rrc", rc); } else { RTMsgError("RTAsn1EncodeWrite failed: %Rrc - %s", rc, StaticErrInfo.szMsg); RTStrmClose(pStrm); } } else RTMsgError("Error opening '%s' for writing: %Rrcs", State.pszOutput, rc); } else RTMsgError("RTAsn1EncodePrepare failed: %Rrc - %s", rc, StaticErrInfo.szMsg); } RTCrTafTrustAnchorInfo_Delete(&TrustAnchor); } else RTMsgError("RTCrTafTrustAnchorInfo_Init failed: %Rrc", rc); RTCrX509Certificate_Delete(&Certificate); return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; } /* * The 'version' command. */ static RTEXITCODE HelpVersion(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { RT_NOREF_PV(enmLevel); RTStrmPrintf(pStrm, "version\n"); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleVersion(int cArgs, char **papszArgs) { RT_NOREF_PV(cArgs); RT_NOREF_PV(papszArgs); #ifndef IN_BLD_PROG /* RTBldCfgVersion or RTBldCfgRevision in build time IPRT lib. */ RTPrintf("%s\n", RTBldCfgVersion()); return RTEXITCODE_SUCCESS; #else return RTEXITCODE_FAILURE; #endif } /** * Command mapping. */ static struct { /** The command. */ const char *pszCmd; /** * Handle the command. * @returns Program exit code. * @param cArgs Number of arguments. * @param papszArgs The argument vector, starting with the command name. */ RTEXITCODE (*pfnHandler)(int cArgs, char **papszArgs); /** * Produce help. * @returns RTEXITCODE_SUCCESS to simplify handling '--help' in the handler. * @param pStrm Where to send help text. * @param enmLevel The level of the help information. */ RTEXITCODE (*pfnHelp)(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel); } /** Mapping commands to handler and helper functions. */ const g_aCommands[] = { { "extract-exe-signer-cert", HandleExtractExeSignerCert, HelpExtractExeSignerCert }, { "extract-signer-root", HandleExtractSignerRoot, HelpExtractSignerRoot }, { "extract-timestamp-root", HandleExtractTimestampRoot, HelpExtractTimestampRoot }, { "extract-exe-signature", HandleExtractExeSignature, HelpExtractExeSignature }, { "add-nested-exe-signature", HandleAddNestedExeSignature, HelpAddNestedExeSignature }, { "add-nested-cat-signature", HandleAddNestedCatSignature, HelpAddNestedCatSignature }, #ifndef IPRT_SIGNTOOL_NO_SIGNING { "add-timestamp-exe-signature", HandleAddTimestampExeSignature, HelpAddTimestampExeSignature }, { "sign", HandleSign, HelpSign }, #endif #ifndef IPRT_IN_BUILD_TOOL { "verify-exe", HandleVerifyExe, HelpVerifyExe }, #endif { "show-exe", HandleShowExe, HelpShowExe }, { "show-cat", HandleShowCat, HelpShowCat }, { "hash-exe", HandleHashExe, HelpHashExe }, { "make-tainfo", HandleMakeTaInfo, HelpMakeTaInfo }, { "help", HandleHelp, HelpHelp }, { "--help", HandleHelp, NULL }, { "-h", HandleHelp, NULL }, { "version", HandleVersion, HelpVersion }, { "--version", HandleVersion, NULL }, { "-V", HandleVersion, NULL }, }; /* * The 'help' command. */ static RTEXITCODE HelpHelp(PRTSTREAM pStrm, RTSIGNTOOLHELP enmLevel) { RT_NOREF_PV(enmLevel); RTStrmPrintf(pStrm, "help [cmd-patterns]\n"); return RTEXITCODE_SUCCESS; } static RTEXITCODE HandleHelp(int cArgs, char **papszArgs) { PRTSTREAM const pStrm = g_pStdOut; RTSIGNTOOLHELP enmLevel = cArgs <= 1 ? RTSIGNTOOLHELP_USAGE : RTSIGNTOOLHELP_FULL; uint32_t cShowed = 0; uint32_t cchWidth; if (RT_FAILURE(RTStrmQueryTerminalWidth(g_pStdOut, &cchWidth))) cchWidth = 80; RTStrmPrintf(pStrm, "Usage: RTSignTool [command-options]\n" " or: RTSignTool <-V|--version|version>\n" " or: RTSignTool <-h|--help|help> [command-pattern [..]]\n" "\n" ); if (enmLevel == RTSIGNTOOLHELP_USAGE) RTStrmPrintf(pStrm, "Syntax summary for the RTSignTool commands:\n"); for (uint32_t iCmd = 0; iCmd < RT_ELEMENTS(g_aCommands); iCmd++) { if (g_aCommands[iCmd].pfnHelp) { bool fShow = false; if (cArgs <= 1) fShow = true; else for (int iArg = 1; iArg < cArgs; iArg++) if (RTStrSimplePatternMultiMatch(papszArgs[iArg], RTSTR_MAX, g_aCommands[iCmd].pszCmd, RTSTR_MAX, NULL)) { fShow = true; break; } if (fShow) { if (enmLevel == RTSIGNTOOLHELP_FULL) RTPrintf("%.*s\n", RT_MIN(cchWidth, 100), "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); g_aCommands[iCmd].pfnHelp(pStrm, enmLevel); cShowed++; } } } return cShowed ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; } int main(int argc, char **argv) { int rc = RTR3InitExe(argc, &argv, 0); if (RT_FAILURE(rc)) return RTMsgInitFailure(rc); /* * Parse global arguments. */ int iArg = 1; /* none presently. */ /* * Command dispatcher. */ if (iArg < argc) { const char *pszCmd = argv[iArg]; uint32_t i = RT_ELEMENTS(g_aCommands); while (i-- > 0) if (!strcmp(g_aCommands[i].pszCmd, pszCmd)) return g_aCommands[i].pfnHandler(argc - iArg, &argv[iArg]); RTMsgError("Unknown command '%s'.", pszCmd); } else RTMsgError("No command given. (try --help)"); return RTEXITCODE_SYNTAX; }