Reactos
at master 508 lines 15 kB view raw
1/* 2 * PROJECT: ReactOS certutil 3 * LICENSE: MIT (https://spdx.org/licenses/MIT) 4 * PURPOSE: CertUtil asn implementation 5 * COPYRIGHT: Copyright 2020 Mark Jansen (mark.jansen@reactos.org) 6 * 7 * NOTES: 8 * To keep it simple, Tag and Class are combined in one identifier 9 * See for more details: 10 * https://en.wikipedia.org/wiki/X.690#BER_encoding 11 * https://www.strozhevsky.com/free_docs/asn1_by_simple_words.pdf 12 * http://mikk.net/~chris/asn1.pdf 13 * 14 * And for a test suite: 15 * https://github.com/YuryStrozhevsky/asn1-test-suite 16 */ 17 18#include "precomp.h" 19#include <math.h> 20#include <wincrypt.h> 21#include <stdlib.h> 22 23 24#define ASN_TAG_IS_CONSTRUCTED 0x20 25 26 27#define ASN_TAG_BITSTRING 0x03 28#define ASN_TAG_OCTET_STRING 0x04 29#define ASN_TAG_OBJECT_ID 0x06 30 31#define ASN_TAG_SEQUENCE_RAW 0x10 32#define ASN_TAG_SET_RAW 0x11 33 34#define ASN_TAG_SEQUENCE 0x30 35#define ASN_TAG_SET 0x31 36 37 38#define ASN_TAG_CONTEXT_SPECIFIC 0x80 39#define ASN_TAG_CONTEXT_SPECIFIC_N(n) (ASN_TAG_CONTEXT_SPECIFIC | (n)) 40 41#define ASN_TAG_OPTIONAL 0xA0 42#define ASN_TAG_OPTIONAL_N(n) (ASN_TAG_OPTIONAL | (n)) 43 44/* NOTE: These names are not the names listed in f.e. the wikipedia pages, 45 they are made to look like MS's names for this */ 46LPCWSTR TagToName(DWORD dwTag) 47{ 48 switch (dwTag) 49 { 50 case 0x0: return L"EOC"; 51 case 0x1: return L"BOOL"; 52 case 0x2: return L"INTEGER"; 53 case ASN_TAG_BITSTRING: return L"BIT_STRING"; 54 case ASN_TAG_OCTET_STRING: return L"OCTET_STRING"; 55 case 0x5: return L"NULL"; 56 case ASN_TAG_OBJECT_ID: return L"OBJECT_ID"; 57 case 0x7: return L"Object Descriptor"; 58 case 0x8: return L"EXTERNAL"; 59 case 0x9: return L"REAL"; 60 case 0xA: return L"ENUMERATED"; 61 case 0xB: return L"EMBEDDED PDV"; 62 case 0xC: return L"UTF8String"; 63 case 0xD: return L"RELATIVE-OID"; 64 case 0xE: return L"TIME"; 65 case 0xF: return L"Reserved"; 66 case ASN_TAG_SEQUENCE_RAW: __debugbreak(); return L"SEQUENCE_RAW"; 67 case ASN_TAG_SET_RAW: __debugbreak(); return L"SET_RAW"; 68 case 0x12: return L"NumericString"; 69 case 0x13: return L"PRINTABLE_STRING"; 70 case 0x14: return L"T61String"; 71 case 0x15: return L"VideotexString"; 72 case 0x16: return L"IA5String"; 73 case 0x17: return L"UTC_TIME"; 74 case 0x18: return L"GeneralizedTime"; 75 case 0x19: return L"GraphicString"; 76 case 0x1A: return L"VisibleString"; 77 case 0x1B: return L"GeneralString"; 78 case 0x1C: return L"UniversalString"; 79 case 0x1D: return L"CHARACTER STRING"; 80 case 0x1E: return L"BMPString"; 81 case 0x1F: return L"DATE"; 82 case 0x20: return L"CONSTRUCTED"; 83 84 case ASN_TAG_SEQUENCE: return L"SEQUENCE"; 85 case ASN_TAG_SET: return L"SET"; 86 87 88 case ASN_TAG_CONTEXT_SPECIFIC_N(0): return L"CONTEXT_SPECIFIC[0]"; 89 case ASN_TAG_CONTEXT_SPECIFIC_N(1): return L"CONTEXT_SPECIFIC[1]"; 90 case ASN_TAG_CONTEXT_SPECIFIC_N(2): return L"CONTEXT_SPECIFIC[2]"; 91 case ASN_TAG_CONTEXT_SPECIFIC_N(3): return L"CONTEXT_SPECIFIC[3]"; 92 case ASN_TAG_CONTEXT_SPECIFIC_N(4): return L"CONTEXT_SPECIFIC[4]"; 93 case ASN_TAG_CONTEXT_SPECIFIC_N(5): return L"CONTEXT_SPECIFIC[5]"; 94 case ASN_TAG_CONTEXT_SPECIFIC_N(6): return L"CONTEXT_SPECIFIC[6]"; 95 case ASN_TAG_CONTEXT_SPECIFIC_N(7): return L"CONTEXT_SPECIFIC[7]"; 96 case ASN_TAG_CONTEXT_SPECIFIC_N(8): return L"CONTEXT_SPECIFIC[8]"; 97 case ASN_TAG_CONTEXT_SPECIFIC_N(9): return L"CONTEXT_SPECIFIC[9]"; 98 /* Experiments show that Windows' certutil only goes up to 9 */ 99 100 101 case ASN_TAG_OPTIONAL_N(0): return L"OPTIONAL[0]"; 102 case ASN_TAG_OPTIONAL_N(1): return L"OPTIONAL[1]"; 103 case ASN_TAG_OPTIONAL_N(2): return L"OPTIONAL[2]"; 104 case ASN_TAG_OPTIONAL_N(3): return L"OPTIONAL[3]"; 105 case ASN_TAG_OPTIONAL_N(4): return L"OPTIONAL[4]"; 106 case ASN_TAG_OPTIONAL_N(5): return L"OPTIONAL[5]"; 107 case ASN_TAG_OPTIONAL_N(6): return L"OPTIONAL[6]"; 108 case ASN_TAG_OPTIONAL_N(7): return L"OPTIONAL[7]"; 109 case ASN_TAG_OPTIONAL_N(8): return L"OPTIONAL[8]"; 110 case ASN_TAG_OPTIONAL_N(9): return L"OPTIONAL[9]"; 111 /* Experiments show that Windows' certutil only goes up to 9 */ 112 113 default: 114 return L"???"; 115 } 116} 117 118BOOL Move(DWORD dwLen, PBYTE& pData, DWORD& dwSize) 119{ 120 if (dwSize < dwLen) 121 return FALSE; 122 123 pData += dwLen; 124 dwSize -= dwLen; 125 126 return TRUE; 127} 128 129BOOL ParseTag(PBYTE& pData, DWORD& dwSize, DWORD& dwTagAndClass) 130{ 131 if (dwSize == 0) 132 return FALSE; 133 134 /* Is this a long form? */ 135 if ((pData[0] & 0x1f) != 0x1f) 136 { 137 /* No, so extract the tag and class (in one identifier) */ 138 dwTagAndClass = pData[0]; 139 return Move(1, pData, dwSize); 140 } 141 142 DWORD dwClass = (pData[0] & 0xE0) >> 5; 143 dwTagAndClass = 0; 144 DWORD n; 145 for (n = 1; n < dwSize; ++n) 146 { 147 dwTagAndClass <<= 7; 148 dwTagAndClass |= (pData[n] & 0x7f); 149 150 if (!(pData[n] & 0x80)) 151 { 152 break; 153 } 154 } 155 156 Move(n, pData, dwSize); 157 158 /* Any number bigger than this, we shift data out! */ 159 if (n > 4) 160 return FALSE; 161 162 /* Just drop this in the hightest bits*/ 163 dwTagAndClass |= (dwClass << (32-3)); 164 165 return TRUE; 166} 167 168BOOL ParseLength(PBYTE& pData, DWORD& dwSize, DWORD& dwLength) 169{ 170 if (dwSize == 0) 171 return FALSE; 172 173 if (!(pData[0] & 0x80)) 174 { 175 dwLength = pData[0]; 176 return Move(1, pData, dwSize); 177 } 178 179 DWORD dwBytes = pData[0] & 0x7f; 180 if (dwBytes == 0 || dwBytes > 8 || dwBytes + 1 > dwSize) 181 { 182 return FALSE; 183 } 184 185 dwLength = 0; 186 for (DWORD n = 0; n < dwBytes; ++n) 187 { 188 dwLength <<= 8; 189 dwLength += pData[1 + n]; 190 } 191 192 return Move(dwBytes + 1, pData, dwSize); 193} 194 195 196DWORD HexDump(PBYTE pRoot, PBYTE pData, DWORD dwSize, PWSTR wszPrefix) 197{ 198 while (TRUE) 199 { 200 SIZE_T Address = pData - pRoot; 201 ConPrintf(StdOut, L"%04x: ", Address); 202 ConPuts(StdOut, wszPrefix); 203 204 for (DWORD n = 0; n < min(dwSize, 0x10); ++n) 205 { 206 ConPrintf(StdOut, L"%02x ", pData[n]); 207 } 208 209 if (dwSize <= 0x10) 210 break; 211 212 Move(0x10, pData, dwSize); 213 ConPuts(StdOut, L"\n"); 214 } 215 216 return 3 * dwSize; 217} 218 219void PrintTag(PBYTE pRoot, PBYTE pHeader, DWORD dwTag, DWORD dwTagLength, PBYTE pData, PWSTR wszPrefix) 220{ 221 DWORD dwRemainder = HexDump(pRoot, pHeader, pData - pHeader, wszPrefix); 222 223 LPCWSTR wszTag = TagToName(dwTag); 224 DWORD dwPadding = dwRemainder + wcslen(wszPrefix); 225 while (dwPadding > 50) 226 dwPadding -= 50; 227 ConPrintf(StdOut, L"%*s; %s (%x Bytes)\n", 50 - dwPadding, L"", wszTag, dwTagLength); 228} 229 230struct OID_NAMES 231{ 232 CHAR* Oid; 233 LPCWSTR Names[20]; 234 DWORD NumberOfNames; 235}; 236 237BOOL WINAPI CryptOIDEnumCallback(_In_ PCCRYPT_OID_INFO pInfo, _Inout_opt_ void *pvArg) 238{ 239 OID_NAMES* Names = (OID_NAMES*)pvArg; 240 241 if (pInfo && pInfo->pszOID && !_stricmp(pInfo->pszOID, Names->Oid)) 242 { 243 if (Names->NumberOfNames < RTL_NUMBER_OF(Names->Names)) 244 { 245 for (DWORD n = 0; n < Names->NumberOfNames; ++n) 246 { 247 // We already have this.. 248 if (!_wcsicmp(Names->Names[n], pInfo->pwszName)) 249 return TRUE; 250 } 251 252 Names->Names[Names->NumberOfNames++] = pInfo->pwszName; 253 } 254 } 255 256 return TRUE; 257} 258 259void PrintOID(PBYTE pRoot, PBYTE pHeader, PBYTE pData, DWORD dwSize, PWSTR wszPrefix) 260{ 261 /* CryptFindOIDInfo expects the OID to be in ANSI.. */ 262 CHAR szOID[250]; 263 CHAR* ptr = szOID; 264 size_t cchRemaining = RTL_NUMBER_OF(szOID); 265 266 /* CryptFindOIDInfo just returns the first, we want multiple */ 267 OID_NAMES Names = {0}; 268 269 if (dwSize == 0) 270 return; 271 272 DWORD dwValue = 0, count = 0; 273 for (DWORD n = 0; n < dwSize; ++n) 274 { 275 dwValue <<= 7; 276 dwValue |= pData[n] & 0x7f; 277 278 if (pData[n] & 0x80) 279 { 280 if (++count >= 4) 281 break; 282 continue; 283 } 284 count = 0; 285 286 /* First & second octet have a special encoding */ 287 if (ptr == szOID) 288 { 289 DWORD id1 = dwValue / 40; 290 DWORD id2 = dwValue % 40; 291 292 /* The first one can only be 0, 1 or 2, so handle special case: tc24.ber */ 293 if (id1 > 2) 294 { 295 id2 += (id1 - 2) * 40; 296 id1 = 2; 297 } 298 StringCchPrintfExA(ptr, cchRemaining, &ptr, &cchRemaining, 0, "%d.%d", id1, id2); 299 } 300 else 301 { 302 StringCchPrintfExA(ptr, cchRemaining, &ptr, &cchRemaining, 0, ".%d", dwValue); 303 } 304 305 dwValue = 0; 306 } 307 308 if (dwValue || count) 309 { 310 /* We cannot format this, so just add abort */ 311 return; 312 } 313 314 SIZE_T Address = pData - pRoot; 315 /* Pad with spaces instead of printing the address again */ 316 DWORD addrDigits = (DWORD)log10((double)Address) + 1; 317 ConPrintf(StdOut, L"%*s ", max(addrDigits, 4), L""); 318 ConPrintf(StdOut, L"%s; %S", wszPrefix, szOID); 319 320 Names.Oid = szOID; 321 322 /* The order does not match a naive call with '0'... */ 323 CryptEnumOIDInfo(0, 0, &Names, CryptOIDEnumCallback); 324 325 for (DWORD n = 0; n < Names.NumberOfNames; ++n) 326 { 327 if (n == 0) 328 ConPrintf(StdOut, L" %s", Names.Names[n]); 329 else if (n == 1) 330 ConPrintf(StdOut, L" (%s", Names.Names[n]); 331 else 332 ConPrintf(StdOut, L" / %s", Names.Names[n]); 333 } 334 335 ConPrintf(StdOut, L"%s\n", Names.NumberOfNames > 1 ? L")" : L""); 336} 337 338 339BOOL ParseAsn(PBYTE pRoot, PBYTE pData, DWORD dwSize, PWSTR wszPrefix, BOOL fPrint) 340{ 341 while (dwSize) 342 { 343 PBYTE pHeader = pData; 344 DWORD dwTagAndClass; 345 346 if (!ParseTag(pData, dwSize, dwTagAndClass)) 347 { 348 if (fPrint) 349 ConPrintf(StdOut, L"CertUtil: -asn command failed to parse tag near 0x%x\n", pHeader - pRoot); 350 return FALSE; 351 } 352 353 DWORD dwTagLength; 354 if (!ParseLength(pData, dwSize, dwTagLength)) 355 { 356 if (fPrint) 357 ConPrintf(StdOut, L"CertUtil: -asn command failed to parse tag length near 0x%x\n", pHeader - pRoot); 358 return FALSE; 359 } 360 361 if (dwTagLength > dwSize) 362 { 363 if (fPrint) 364 ConPrintf(StdOut, L"CertUtil: -asn command malformed tag length near 0x%x\n", pHeader - pRoot); 365 return FALSE; 366 } 367 368 369 if (fPrint) 370 PrintTag(pRoot, pHeader, dwTagAndClass, dwTagLength, pData, wszPrefix); 371 372 size_t len = wcslen(wszPrefix); 373 StringCchCatW(wszPrefix, MAX_PATH, dwTagLength != dwSize ? L"| " : L" "); 374 375 if (dwTagAndClass & ASN_TAG_IS_CONSTRUCTED) 376 { 377 if (!ParseAsn(pRoot, pData, dwTagLength, wszPrefix, fPrint)) 378 { 379 return FALSE; 380 } 381 } 382 else 383 { 384 if (fPrint) 385 { 386 /* Special case for a bit string / octet string */ 387 if ((dwTagAndClass == ASN_TAG_BITSTRING || dwTagAndClass == ASN_TAG_OCTET_STRING) && dwTagLength) 388 { 389 if (dwTagAndClass == ASN_TAG_BITSTRING) 390 { 391 /* First, we print the 'unused bits' field of the bit string */ 392 HexDump(pRoot, pData, 1, wszPrefix); 393 ConPuts(StdOut, L"\n"); 394 395 /* Move past it */ 396 Move(1, pData, dwSize); 397 dwTagLength--; 398 } 399 400 /* Do we have any data left? */ 401 if (dwTagLength) 402 { 403 /* Try to parse this as ASN */ 404 if (ParseAsn(pRoot, pData, dwTagLength, wszPrefix, FALSE)) 405 { 406 /* We succeeded, this _could_ be ASN, so display it as if it is */ 407 if (!ParseAsn(pRoot, pData, dwTagLength, wszPrefix, TRUE)) 408 { 409 /* Uhhh, did someone edit the data? */ 410 ConPrintf(StdOut, L"CertUtil: -asn command unexpected failure parsing tag near 0x%x\n", pData - pRoot); 411 return FALSE; 412 } 413 414 /* Move past what we just parsed */ 415 Move(dwTagLength, pData, dwSize); 416 /* Lie about this so that we don't also print a hexdump */ 417 dwTagLength = 0; 418 } 419 } 420 } 421 422 /* Is there any data (left) to print? */ 423 if (dwTagLength) 424 { 425 HexDump(pRoot, pData, dwTagLength, wszPrefix); 426 ConPuts(StdOut, L"\n"); 427 428 StringCchCatW(wszPrefix, MAX_PATH, L" "); 429 430 /* Do we have additional formatters? */ 431 switch (dwTagAndClass) 432 { 433 case ASN_TAG_OBJECT_ID: 434 PrintOID(pRoot, pHeader, pData, dwTagLength, wszPrefix); 435 break; 436 default: 437 break; 438 } 439 } 440 } 441 } 442 443 wszPrefix[len] = '\0'; 444 445 if (!Move(dwTagLength, pData, dwSize)) 446 { 447 /* This should not be possible, it was checked before! */ 448 return FALSE; 449 } 450 } 451 452 return TRUE; 453} 454 455 456BOOL asn_dump(LPCWSTR Filename) 457{ 458 HANDLE hFile = CreateFileW(Filename, GENERIC_READ, FILE_SHARE_READ, NULL, 459 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); 460 461 if (hFile == INVALID_HANDLE_VALUE) 462 { 463 ConPrintf(StdOut, L"CertUtil: -asn command failed to open: %d\n", GetLastError()); 464 return FALSE; 465 } 466 467 DWORD dwSize = GetFileSize(hFile, NULL); 468 if (dwSize == INVALID_FILE_SIZE) 469 { 470 ConPrintf(StdOut, L"CertUtil: -asn command failed to get file size: %d\n", GetLastError()); 471 CloseHandle(hFile); 472 return FALSE; 473 } 474 475 if (dwSize == 0) 476 { 477 ConPrintf(StdOut, L"CertUtil: -asn command got an empty file\n"); 478 CloseHandle(hFile); 479 return FALSE; 480 } 481 482 PBYTE pData = (PBYTE)LocalAlloc(0, dwSize); 483 if (!pData) 484 { 485 ConPrintf(StdOut, L"CertUtil: -asn command failed to allocate: %d\n", GetLastError()); 486 CloseHandle(hFile); 487 return FALSE; 488 } 489 490 DWORD cbRead; 491 BOOL fRead = ReadFile(hFile, pData, dwSize, &cbRead, NULL); 492 DWORD dwErr = GetLastError(); 493 CloseHandle(hFile); 494 495 if (!fRead || cbRead != dwSize) 496 { 497 ConPrintf(StdOut, L"CertUtil: -asn command failed to read: %d\n", dwErr); 498 LocalFree(pData); 499 return FALSE; 500 } 501 502 WCHAR Buffer[MAX_PATH] = {0}; 503 BOOL fSucceeded = ParseAsn(pData, pData, dwSize, Buffer, TRUE); 504 505 LocalFree(pData); 506 return fSucceeded; 507} 508