diff --git a/base/applications/cmdutils/certutil/CMakeLists.txt b/base/applications/cmdutils/certutil/CMakeLists.txt index bb9cd3a2fde..bad17571c46 100644 --- a/base/applications/cmdutils/certutil/CMakeLists.txt +++ b/base/applications/cmdutils/certutil/CMakeLists.txt @@ -2,12 +2,14 @@ include_directories(${REACTOS_SOURCE_DIR}/sdk/lib/conutils) list(APPEND SOURCE - certutil.c + asn.cpp + certutil.cpp + hashfile.cpp precomp.h) add_executable(certutil ${SOURCE}) set_module_type(certutil win32cui UNICODE) target_link_libraries(certutil conutils ${PSEH_LIB}) -add_importlibs(certutil advapi32 msvcrt kernel32) +add_importlibs(certutil crypt32 advapi32 msvcrt kernel32) add_pch(certutil precomp.h SOURCE) add_cd_file(TARGET certutil DESTINATION reactos/system32 FOR all) diff --git a/base/applications/cmdutils/certutil/asn.cpp b/base/applications/cmdutils/certutil/asn.cpp new file mode 100644 index 00000000000..030691d4f01 --- /dev/null +++ b/base/applications/cmdutils/certutil/asn.cpp @@ -0,0 +1,508 @@ +/* + * PROJECT: ReactOS certutil + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: CertUtil asn implementation + * COPYRIGHT: Copyright 2020 Mark Jansen (mark.jansen@reactos.org) + * + * NOTES: + * To keep it simple, Tag and Class are combined in one identifier + * See for more details: + * https://en.wikipedia.org/wiki/X.690#BER_encoding + * https://www.strozhevsky.com/free_docs/asn1_by_simple_words.pdf + * http://mikk.net/~chris/asn1.pdf + * + * And for a test suite: + * https://github.com/YuryStrozhevsky/asn1-test-suite + */ + +#include "precomp.h" +#include +#include +#include + + +#define ASN_TAG_IS_CONSTRUCTED 0x20 + + +#define ASN_TAG_BITSTRING 0x03 +#define ASN_TAG_OCTET_STRING 0x04 +#define ASN_TAG_OBJECT_ID 0x06 + +#define ASN_TAG_SEQUENCE_RAW 0x10 +#define ASN_TAG_SET_RAW 0x11 + +#define ASN_TAG_SEQUENCE 0x30 +#define ASN_TAG_SET 0x31 + + +#define ASN_TAG_CONTEXT_SPECIFIC 0x80 +#define ASN_TAG_CONTEXT_SPECIFIC_N(n) (ASN_TAG_CONTEXT_SPECIFIC | (n)) + +#define ASN_TAG_OPTIONAL 0xA0 +#define ASN_TAG_OPTIONAL_N(n) (ASN_TAG_OPTIONAL | (n)) + +/* NOTE: These names are not the names listed in f.e. the wikipedia pages, + they are made to look like MS's names for this */ +LPCWSTR TagToName(DWORD dwTag) +{ + switch (dwTag) + { + case 0x0: return L"EOC"; + case 0x1: return L"BOOL"; + case 0x2: return L"INTEGER"; + case ASN_TAG_BITSTRING: return L"BIT_STRING"; + case ASN_TAG_OCTET_STRING: return L"OCTET_STRING"; + case 0x5: return L"NULL"; + case ASN_TAG_OBJECT_ID: return L"OBJECT_ID"; + case 0x7: return L"Object Descriptor"; + case 0x8: return L"EXTERNAL"; + case 0x9: return L"REAL"; + case 0xA: return L"ENUMERATED"; + case 0xB: return L"EMBEDDED PDV"; + case 0xC: return L"UTF8String"; + case 0xD: return L"RELATIVE-OID"; + case 0xE: return L"TIME"; + case 0xF: return L"Reserved"; + case ASN_TAG_SEQUENCE_RAW: __debugbreak(); return L"SEQUENCE_RAW"; + case ASN_TAG_SET_RAW: __debugbreak(); return L"SET_RAW"; + case 0x12: return L"NumericString"; + case 0x13: return L"PRINTABLE_STRING"; + case 0x14: return L"T61String"; + case 0x15: return L"VideotexString"; + case 0x16: return L"IA5String"; + case 0x17: return L"UTC_TIME"; + case 0x18: return L"GeneralizedTime"; + case 0x19: return L"GraphicString"; + case 0x1A: return L"VisibleString"; + case 0x1B: return L"GeneralString"; + case 0x1C: return L"UniversalString"; + case 0x1D: return L"CHARACTER STRING"; + case 0x1E: return L"BMPString"; + case 0x1F: return L"DATE"; + case 0x20: return L"CONSTRUCTED"; + + case ASN_TAG_SEQUENCE: return L"SEQUENCE"; + case ASN_TAG_SET: return L"SET"; + + + case ASN_TAG_CONTEXT_SPECIFIC_N(0): return L"CONTEXT_SPECIFIC[0]"; + case ASN_TAG_CONTEXT_SPECIFIC_N(1): return L"CONTEXT_SPECIFIC[1]"; + case ASN_TAG_CONTEXT_SPECIFIC_N(2): return L"CONTEXT_SPECIFIC[2]"; + case ASN_TAG_CONTEXT_SPECIFIC_N(3): return L"CONTEXT_SPECIFIC[3]"; + case ASN_TAG_CONTEXT_SPECIFIC_N(4): return L"CONTEXT_SPECIFIC[4]"; + case ASN_TAG_CONTEXT_SPECIFIC_N(5): return L"CONTEXT_SPECIFIC[5]"; + case ASN_TAG_CONTEXT_SPECIFIC_N(6): return L"CONTEXT_SPECIFIC[6]"; + case ASN_TAG_CONTEXT_SPECIFIC_N(7): return L"CONTEXT_SPECIFIC[7]"; + case ASN_TAG_CONTEXT_SPECIFIC_N(8): return L"CONTEXT_SPECIFIC[8]"; + case ASN_TAG_CONTEXT_SPECIFIC_N(9): return L"CONTEXT_SPECIFIC[9]"; + /* Experiments show that Windows' certutil only goes up to 9 */ + + + case ASN_TAG_OPTIONAL_N(0): return L"OPTIONAL[0]"; + case ASN_TAG_OPTIONAL_N(1): return L"OPTIONAL[1]"; + case ASN_TAG_OPTIONAL_N(2): return L"OPTIONAL[2]"; + case ASN_TAG_OPTIONAL_N(3): return L"OPTIONAL[3]"; + case ASN_TAG_OPTIONAL_N(4): return L"OPTIONAL[4]"; + case ASN_TAG_OPTIONAL_N(5): return L"OPTIONAL[5]"; + case ASN_TAG_OPTIONAL_N(6): return L"OPTIONAL[6]"; + case ASN_TAG_OPTIONAL_N(7): return L"OPTIONAL[7]"; + case ASN_TAG_OPTIONAL_N(8): return L"OPTIONAL[8]"; + case ASN_TAG_OPTIONAL_N(9): return L"OPTIONAL[9]"; + /* Experiments show that Windows' certutil only goes up to 9 */ + + default: + return L"???"; + } +} + +BOOL Move(DWORD dwLen, PBYTE& pData, DWORD& dwSize) +{ + if (dwSize < dwLen) + return FALSE; + + pData += dwLen; + dwSize -= dwLen; + + return TRUE; +} + +BOOL ParseTag(PBYTE& pData, DWORD& dwSize, DWORD& dwTagAndClass) +{ + if (dwSize == 0) + return FALSE; + + /* Is this a long form? */ + if ((pData[0] & 0x1f) != 0x1f) + { + /* No, so extract the tag and class (in one identifier) */ + dwTagAndClass = pData[0]; + return Move(1, pData, dwSize); + } + + DWORD dwClass = (pData[0] & 0xE0) >> 5; + dwTagAndClass = 0; + DWORD n; + for (n = 1; n < dwSize; ++n) + { + dwTagAndClass <<= 7; + dwTagAndClass |= (pData[n] & 0x7f); + + if (!(pData[n] & 0x80)) + { + break; + } + } + + Move(n, pData, dwSize); + + /* Any number bigger than this, we shift data out! */ + if (n > 4) + return FALSE; + + /* Just drop this in the hightest bits*/ + dwTagAndClass |= (dwClass << (32-3)); + + return TRUE; +} + +BOOL ParseLength(PBYTE& pData, DWORD& dwSize, DWORD& dwLength) +{ + if (dwSize == 0) + return FALSE; + + if (!(pData[0] & 0x80)) + { + dwLength = pData[0]; + return Move(1, pData, dwSize); + } + + DWORD dwBytes = pData[0] & 0x7f; + if (dwBytes == 0 || dwBytes > 8 || dwBytes + 1 > dwSize) + { + return FALSE; + } + + dwLength = 0; + for (DWORD n = 0; n < dwBytes; ++n) + { + dwLength <<= 8; + dwLength += pData[1 + n]; + } + + return Move(dwBytes + 1, pData, dwSize); +} + + +DWORD HexDump(PBYTE pRoot, PBYTE pData, DWORD dwSize, PWSTR wszPrefix) +{ + while (TRUE) + { + SIZE_T Address = pData - pRoot; + ConPrintf(StdOut, L"%04x: ", Address); + ConPuts(StdOut, wszPrefix); + + for (DWORD n = 0; n < min(dwSize, 0x10); ++n) + { + ConPrintf(StdOut, L"%02x ", pData[n]); + } + + if (dwSize <= 0x10) + break; + + Move(0x10, pData, dwSize); + ConPuts(StdOut, L"\n"); + } + + return 3 * dwSize; +} + +void PrintTag(PBYTE pRoot, PBYTE pHeader, DWORD dwTag, DWORD dwTagLength, PBYTE pData, PWSTR wszPrefix) +{ + DWORD dwRemainder = HexDump(pRoot, pHeader, pData - pHeader, wszPrefix); + + LPCWSTR wszTag = TagToName(dwTag); + DWORD dwPadding = dwRemainder + wcslen(wszPrefix); + while (dwPadding > 50) + dwPadding -= 50; + ConPrintf(StdOut, L"%*s; %s (%x Bytes)\n", 50 - dwPadding, L"", wszTag, dwTagLength); +} + +struct OID_NAMES +{ + CHAR* Oid; + LPCWSTR Names[20]; + DWORD NumberOfNames; +}; + +BOOL WINAPI CryptOIDEnumCallback(_In_ PCCRYPT_OID_INFO pInfo, _Inout_opt_ void *pvArg) +{ + OID_NAMES* Names = (OID_NAMES*)pvArg; + + if (pInfo && pInfo->pszOID && !_stricmp(pInfo->pszOID, Names->Oid)) + { + if (Names->NumberOfNames < RTL_NUMBER_OF(Names->Names)) + { + for (DWORD n = 0; n < Names->NumberOfNames; ++n) + { + // We already have this.. + if (!_wcsicmp(Names->Names[n], pInfo->pwszName)) + return TRUE; + } + + Names->Names[Names->NumberOfNames++] = pInfo->pwszName; + } + } + + return TRUE; +} + +void PrintOID(PBYTE pRoot, PBYTE pHeader, PBYTE pData, DWORD dwSize, PWSTR wszPrefix) +{ + /* CryptFindOIDInfo expects the OID to be in ANSI.. */ + CHAR szOID[250]; + CHAR* ptr = szOID; + size_t cchRemaining = RTL_NUMBER_OF(szOID); + + /* CryptFindOIDInfo just returns the first, we want multiple */ + OID_NAMES Names = {0}; + + if (dwSize == 0) + return; + + DWORD dwValue = 0, count = 0; + for (DWORD n = 0; n < dwSize; ++n) + { + dwValue <<= 7; + dwValue |= pData[n] & 0x7f; + + if (pData[n] & 0x80) + { + if (++count >= 4) + break; + continue; + } + count = 0; + + /* First & second octet have a special encoding */ + if (ptr == szOID) + { + DWORD id1 = dwValue / 40; + DWORD id2 = dwValue % 40; + + /* The first one can only be 0, 1 or 2, so handle special case: tc24.ber */ + if (id1 > 2) + { + id2 += (id1 - 2) * 40; + id1 = 2; + } + StringCchPrintfExA(ptr, cchRemaining, &ptr, &cchRemaining, 0, "%d.%d", id1, id2); + } + else + { + StringCchPrintfExA(ptr, cchRemaining, &ptr, &cchRemaining, 0, ".%d", dwValue); + } + + dwValue = 0; + } + + if (dwValue || count) + { + /* We cannot format this, so just add abort */ + return; + } + + SIZE_T Address = pData - pRoot; + /* Pad with spaces instead of printing the address again */ + DWORD addrDigits = (DWORD)log10((double)Address) + 1; + ConPrintf(StdOut, L"%*s ", max(addrDigits, 4), L""); + ConPrintf(StdOut, L"%s; %S", wszPrefix, szOID); + + Names.Oid = szOID; + + /* The order does not match a naive call with '0'... */ + CryptEnumOIDInfo(0, 0, &Names, CryptOIDEnumCallback); + + for (DWORD n = 0; n < Names.NumberOfNames; ++n) + { + if (n == 0) + ConPrintf(StdOut, L" %s", Names.Names[n]); + else if (n == 1) + ConPrintf(StdOut, L" (%s", Names.Names[n]); + else + ConPrintf(StdOut, L" / %s", Names.Names[n]); + } + + ConPrintf(StdOut, L"%s\n", Names.NumberOfNames > 1 ? L")" : L""); +} + + +BOOL ParseAsn(PBYTE pRoot, PBYTE pData, DWORD dwSize, PWSTR wszPrefix, BOOL fPrint) +{ + while (dwSize) + { + PBYTE pHeader = pData; + DWORD dwTagAndClass; + + if (!ParseTag(pData, dwSize, dwTagAndClass)) + { + if (fPrint) + ConPrintf(StdOut, L"CertUtil: -asn command failed to parse tag near 0x%x\n", pHeader - pRoot); + return FALSE; + } + + DWORD dwTagLength; + if (!ParseLength(pData, dwSize, dwTagLength)) + { + if (fPrint) + ConPrintf(StdOut, L"CertUtil: -asn command failed to parse tag length near 0x%x\n", pHeader - pRoot); + return FALSE; + } + + if (dwTagLength > dwSize) + { + if (fPrint) + ConPrintf(StdOut, L"CertUtil: -asn command malformed tag length near 0x%x\n", pHeader - pRoot); + return FALSE; + } + + + if (fPrint) + PrintTag(pRoot, pHeader, dwTagAndClass, dwTagLength, pData, wszPrefix); + + size_t len = wcslen(wszPrefix); + StringCchCatW(wszPrefix, MAX_PATH, dwTagLength != dwSize ? L"| " : L" "); + + if (dwTagAndClass & ASN_TAG_IS_CONSTRUCTED) + { + if (!ParseAsn(pRoot, pData, dwTagLength, wszPrefix, fPrint)) + { + return FALSE; + } + } + else + { + if (fPrint) + { + /* Special case for a bit string / octet string */ + if ((dwTagAndClass == ASN_TAG_BITSTRING || dwTagAndClass == ASN_TAG_OCTET_STRING) && dwTagLength) + { + if (dwTagAndClass == ASN_TAG_BITSTRING) + { + /* First, we print the 'unused bits' field of the bit string */ + HexDump(pRoot, pData, 1, wszPrefix); + ConPuts(StdOut, L"\n"); + + /* Move past it */ + Move(1, pData, dwSize); + dwTagLength--; + } + + /* Do we have any data left? */ + if (dwTagLength) + { + /* Try to parse this as ASN */ + if (ParseAsn(pRoot, pData, dwTagLength, wszPrefix, FALSE)) + { + /* We succeeded, this _could_ be ASN, so display it as if it is */ + if (!ParseAsn(pRoot, pData, dwTagLength, wszPrefix, TRUE)) + { + /* Uhhh, did someone edit the data? */ + ConPrintf(StdOut, L"CertUtil: -asn command unexpected failure parsing tag near 0x%x\n", pData - pRoot); + return FALSE; + } + + /* Move past what we just parsed */ + Move(dwTagLength, pData, dwSize); + /* Lie about this so that we don't also print a hexdump */ + dwTagLength = 0; + } + } + } + + /* Is there any data (left) to print? */ + if (dwTagLength) + { + HexDump(pRoot, pData, dwTagLength, wszPrefix); + ConPuts(StdOut, L"\n"); + + StringCchCatW(wszPrefix, MAX_PATH, L" "); + + /* Do we have additional formatters? */ + switch (dwTagAndClass) + { + case ASN_TAG_OBJECT_ID: + PrintOID(pRoot, pHeader, pData, dwTagLength, wszPrefix); + break; + default: + break; + } + } + } + } + + wszPrefix[len] = '\0'; + + if (!Move(dwTagLength, pData, dwSize)) + { + /* This should not be possible, it was checked before! */ + return FALSE; + } + } + + return TRUE; +} + + +BOOL asn_dump(LPCWSTR Filename) +{ + HANDLE hFile = CreateFileW(Filename, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + { + ConPrintf(StdOut, L"CertUtil: -asn command failed to open: %d\n", GetLastError()); + return FALSE; + } + + DWORD dwSize = GetFileSize(hFile, NULL); + if (dwSize == INVALID_FILE_SIZE) + { + ConPrintf(StdOut, L"CertUtil: -asn command failed to get file size: %d\n", GetLastError()); + CloseHandle(hFile); + return FALSE; + } + + if (dwSize == 0) + { + ConPrintf(StdOut, L"CertUtil: -asn command got an empty file\n"); + CloseHandle(hFile); + return FALSE; + } + + PBYTE pData = (PBYTE)LocalAlloc(0, dwSize); + if (!pData) + { + ConPrintf(StdOut, L"CertUtil: -asn command failed to allocate: %d\n", GetLastError()); + CloseHandle(hFile); + return FALSE; + } + + DWORD cbRead; + BOOL fRead = ReadFile(hFile, pData, dwSize, &cbRead, NULL); + DWORD dwErr = GetLastError(); + CloseHandle(hFile); + + if (!fRead || cbRead != dwSize) + { + ConPrintf(StdOut, L"CertUtil: -asn command failed to read: %d\n", dwErr); + LocalFree(pData); + return FALSE; + } + + WCHAR Buffer[MAX_PATH] = {0}; + BOOL fSucceeded = ParseAsn(pData, pData, dwSize, Buffer, TRUE); + + LocalFree(pData); + return fSucceeded; +} + diff --git a/base/applications/cmdutils/certutil/certutil.cpp b/base/applications/cmdutils/certutil/certutil.cpp new file mode 100644 index 00000000000..85d118961c3 --- /dev/null +++ b/base/applications/cmdutils/certutil/certutil.cpp @@ -0,0 +1,107 @@ +/* + * PROJECT: ReactOS certutil + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: CertUtil commandline handling + * COPYRIGHT: Copyright 2020 Mark Jansen (mark.jansen@reactos.org) + * + * Note: Only -hashfile and -asn are implemented for now, the rest is not present! + */ + +#include "precomp.h" +#include +#include + +typedef struct +{ + LPCWSTR Name; + BOOL (*pfn)(LPCWSTR Filename); +} Verb; + + +Verb verbs[] = { + { L"hashfile", hash_file }, + { L"asn", asn_dump }, +}; + +static void print_usage() +{ + ConPuts(StdOut, L"Verbs:\n"); + ConPuts(StdOut, L" -hashfile -- Display cryptographic hash over a file\n"); + ConPuts(StdOut, L" -asn -- Display ASN.1 encoding of a file\n"); + ConPuts(StdOut, L"\n"); + ConPuts(StdOut, L"CertUtil -? -- Display a list of all verbs\n"); + ConPuts(StdOut, L"CertUtil -hashfile -? -- Display help text for the 'hashfile' verb\n"); +} + + +Verb* MatchVerb(LPCWSTR arg) +{ + if (arg[0] != '-' && arg[0] != '/') + return NULL; + + for (size_t n = 0; n < RTL_NUMBER_OF(verbs); ++n) + { + if (!_wcsicmp(verbs[n].Name, arg + 1)) + { + return verbs + n; + } + } + + return NULL; +} + +int wmain(int argc, WCHAR *argv[]) +{ + int n; + + /* Initialize the Console Standard Streams */ + ConInitStdStreams(); + + if (argc == 1) /* i.e. no commandline arguments given */ + { + print_usage(); + return EXIT_SUCCESS; + } + + for (n = 1; n < argc; ++n) + { + if (!_wcsicmp(argv[n], L"-?")) + { + print_usage(); + return EXIT_SUCCESS; + } + + Verb* verb = MatchVerb(argv[n]); + + if (verb) + { + if (argc != 3) + { + ConPrintf(StdOut, L"CertUtil: -%s expected 1 argument, got %d\n", verb->Name, argc - 2); + return EXIT_FAILURE; + } + + if (!_wcsicmp(argv[n+1], L"-?")) + { + print_usage(); + return EXIT_SUCCESS; + } + + if (!verb->pfn(argv[n+1])) + { + /* The verb prints the failure */ + return EXIT_FAILURE; + } + + ConPrintf(StdOut, L"CertUtil: -%s command completed successfully\n", verb->Name); + return EXIT_SUCCESS; + } + else + { + ConPrintf(StdOut, L"CertUtil: Unknown verb: %s\n", argv[n]); + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} diff --git a/base/applications/cmdutils/certutil/certutil.c b/base/applications/cmdutils/certutil/hashfile.cpp similarity index 53% rename from base/applications/cmdutils/certutil/certutil.c rename to base/applications/cmdutils/certutil/hashfile.cpp index 7d6d2d68987..214ff54bae7 100644 --- a/base/applications/cmdutils/certutil/certutil.c +++ b/base/applications/cmdutils/certutil/hashfile.cpp @@ -1,10 +1,8 @@ /* * PROJECT: ReactOS certutil - * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) - * PURPOSE: CertUtil stub + * LICENSE: MIT (https://spdx.org/licenses/MIT) + * PURPOSE: CertUtil hashfile implementation * COPYRIGHT: Copyright 2020 Mark Jansen (mark.jansen@reactos.org) - * - * Note: Only -hashfile is implemented for now, the rest is not present! */ #include "precomp.h" @@ -12,7 +10,7 @@ #include -static BOOL hash_file(LPCWSTR Filename) +BOOL hash_file(LPCWSTR Filename) { HCRYPTPROV hProv; BOOL bSuccess = FALSE; @@ -87,69 +85,3 @@ static BOOL hash_file(LPCWSTR Filename) return bSuccess; } - -static void print_usage() -{ - ConPuts(StdOut, L"Verbs:\n"); - ConPuts(StdOut, L" -hashfile -- Display cryptographic hash over a file\n"); - ConPuts(StdOut, L"\n"); - ConPuts(StdOut, L"CertUtil -? -- Display a list of all verbs\n"); - ConPuts(StdOut, L"CertUtil -hashfile -? -- Display help text for the 'hashfile' verb\n"); -} - -int wmain(int argc, WCHAR *argv[]) -{ - int n; - - /* Initialize the Console Standard Streams */ - ConInitStdStreams(); - - if (argc == 1) /* i.e. no commandline arguments given */ - { - print_usage(); - return EXIT_SUCCESS; - } - - for (n = 1; n < argc; ++n) - { - if (!_wcsicmp(argv[n], L"-?")) - { - print_usage(); - return EXIT_SUCCESS; - } - else if (!_wcsicmp(argv[n], L"-hashfile")) - { - if (argc == 3) - { - if (!_wcsicmp(argv[n+1], L"-?")) - { - print_usage(); - return EXIT_SUCCESS; - } - else - { - if (!hash_file(argv[n+1])) - { - /* hash_file prints the failure itself */ - return EXIT_FAILURE; - } - - ConPuts(StdOut, L"CertUtil: -hashfile command completed successfully\n"); - return EXIT_SUCCESS; - } - } - else - { - ConPrintf(StdOut, L"CertUtil: -hashfile expected 1 argument, got %d\n", argc - 2); - return EXIT_FAILURE; - } - } - else - { - ConPrintf(StdOut, L"CertUtil: Unknown verb: %s\n", argv[n]); - return EXIT_FAILURE; - } - } - - return EXIT_SUCCESS; -} diff --git a/base/applications/cmdutils/certutil/precomp.h b/base/applications/cmdutils/certutil/precomp.h index 0c8e115bf5e..9818fbee668 100644 --- a/base/applications/cmdutils/certutil/precomp.h +++ b/base/applications/cmdutils/certutil/precomp.h @@ -7,10 +7,14 @@ #include #include -#include -#include +#include #include +BOOL hash_file(LPCWSTR Filename); +BOOL asn_dump(LPCWSTR Filename); + + + #endif /* __CERTUTIL_PRECOMP_H */