Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1365 lines
44 KiB

  1. /*
  2. usage:
  3. winhttpcertcfg [-?] : to view help information
  4. winhttpcertcfg [-i PFXFile | -g | -r | -l]
  5. -c CertLocation\CertStore [-a Account] [-s SubjectStr] [-p PFXPassword]
  6. -i PFXFile : Import cert via specified .pfx filename given
  7. with the option. This option also requires
  8. -a and -c to indicate destination (doesn't support -a
  9. in conjunction with importing just yet).
  10. -g : Grant access to private key for already
  11. installed cert indicated by subject string.
  12. This option also requires -a, -c, and -s.
  13. -r : Remove access to private key for specified
  14. account name and certificate. This option
  15. also requires -a, -c, -s to specify the
  16. account and certificate information.
  17. -l : List accounts that have access to the private
  18. key of enumerated certs specified via other
  19. filter options. This option also requires
  20. -c and -s to specify which certificate should
  21. be queried.
  22. Description of addtional options:
  23. -c LOCAL_MACHINE|CURRENT_USER\CertStore (Example: LOCAL_MACHINE\MY)
  24. -a Account (Represents user or domain account on the machine.
  25. Examples: IWAM_TESTMACHINE, TESTUSER, or TESTDOMAIN\DOMAINUSER)
  26. -s SubjectStr (Case-insensitive search string for finding the first enumerated
  27. certificate with a subject name that contains this substring.)
  28. -p PFXPassword (Password to use when importing a PFX file. Only used with -i.)
  29. */
  30. #include <windows.h>
  31. #include <cryptui.h>
  32. #include <wincrypt.h>
  33. #include <stdio.h>
  34. typedef DWORD (WINAPI* CRYPTUIWIZIMPORT) (DWORD, HWND, LPCWSTR, PCCRYPTUI_WIZ_IMPORT_SRC_INFO, HCERTSTORE);
  35. typedef BOOL (WINAPI* SETSECURITYDESCRIPTORCONTROL) (PSECURITY_DESCRIPTOR,
  36. SECURITY_DESCRIPTOR_CONTROL,
  37. SECURITY_DESCRIPTOR_CONTROL);
  38. static const char gc_szLocalMachine[] = "LOCAL_MACHINE";
  39. static const char gc_szCurrentUser[] = "CURRENT_USER";
  40. enum ARGTYPE
  41. {
  42. ARGS_HELP,
  43. ARGS_IMPORT_PFX,
  44. ARGS_ADD_PRIVATE_KEY_ACCESS,
  45. ARGS_REMOVE_PRIVATE_KEY_ACCESS,
  46. ARGS_LIST_PRIVATE_KEY_ACCESS
  47. };
  48. struct ARGS
  49. {
  50. ARGTYPE Command;
  51. LPWSTR pwszPFXFile;
  52. LPWSTR pwszCertStore;
  53. LPWSTR pwszCertSubject;
  54. LPWSTR pwszPFXPassword;
  55. LPSTR pszDomain;
  56. LPSTR pszAccount;
  57. BOOL fUseLocalMachine;
  58. };
  59. void ParseArguments(int argc, char **argv, ARGS *pArgs);
  60. DWORD AsciiToWideChar(const char * pszA, LPWSTR * ppszW);
  61. DWORD ImportPFXFile(ARGS *pArgs);
  62. VOID DumpSid(BYTE *pSid);
  63. VOID DumpCertInfo(PCCERT_CONTEXT pCertContext);
  64. DWORD GetAccountForSid(BYTE *pSid, LPTSTR *ppszDomain, LPTSTR *ppszAccount);
  65. DWORD GetSidForAccount(LPCTSTR pszDomain, LPCTSTR pszAccount, BYTE **ppSid, char **ppszDomain);
  66. DWORD DumpAccessAllowedList(PACL pDacl);
  67. DWORD DoPrivateKeyAccessAction(ARGS *pArgs);
  68. DWORD ProcessCertContext(PCCERT_CONTEXT pCertContext, BYTE *pSid, ARGTYPE eCommand);
  69. BOOL CheckForRootCert(PCCERT_CONTEXT pCertContext);
  70. DWORD AddPrivateKeyAccess(PACL pDacl, BYTE *pSid, PACL *ppNewDacl);
  71. DWORD RemovePrivateKeyAccess(PACL pDacl, BYTE *pSid);
  72. BOOL MySetSecurityDescriptorControl(PSECURITY_DESCRIPTOR pSD,
  73. SECURITY_DESCRIPTOR_CONTROL ControlBitsOfInterest,
  74. SECURITY_DESCRIPTOR_CONTROL ControlBitsToSet);
  75. void
  76. ParseArguments(int argc, char **argv, ARGS *pArgs)
  77. {
  78. char *pszBreak;
  79. ZeroMemory((PVOID) pArgs, sizeof(ARGS));
  80. pArgs->Command = ARGS_HELP;
  81. pArgs->fUseLocalMachine = TRUE;
  82. for (; argc > 0; argc--, argv++)
  83. {
  84. if (((argv[0][0] != '-') && (argv[0][0] != '/')) || (lstrlen(argv[0]) != 2))
  85. {
  86. goto ErrorExit;
  87. }
  88. switch (tolower(argv[0][1]))
  89. {
  90. case 'l':
  91. pArgs->Command = ARGS_LIST_PRIVATE_KEY_ACCESS;
  92. break;
  93. case 'g':
  94. pArgs->Command = ARGS_ADD_PRIVATE_KEY_ACCESS;
  95. break;
  96. case 'i':
  97. {
  98. TCHAR szShortPath[MAX_PATH];
  99. BOOL fUseConvertedPath = TRUE;
  100. argc--;
  101. argv++;
  102. if (argc == 0)
  103. {
  104. // error: no PFX file specified
  105. goto ErrorExit;
  106. }
  107. // PFX Import API doesn't handle long filenames
  108. if (GetShortPathName(argv[0], szShortPath, MAX_PATH) > MAX_PATH)
  109. {
  110. fUseConvertedPath = FALSE;
  111. }
  112. if (ERROR_SUCCESS == AsciiToWideChar(fUseConvertedPath ? szShortPath : argv[0],
  113. &pArgs->pwszPFXFile))
  114. {
  115. pArgs->Command = ARGS_IMPORT_PFX;
  116. break;
  117. }
  118. else
  119. {
  120. goto ErrorExit;
  121. }
  122. }
  123. case 'r':
  124. pArgs->Command = ARGS_REMOVE_PRIVATE_KEY_ACCESS;
  125. break;
  126. case 'c':
  127. argc--;
  128. argv++;
  129. if (argc > 0 &&
  130. (pszBreak = strchr(argv[0], '\\')) != NULL)
  131. {
  132. if (_strnicmp(argv[0], gc_szLocalMachine, strlen(gc_szLocalMachine)) == 0)
  133. {
  134. pArgs->fUseLocalMachine = TRUE;
  135. }
  136. else if (_strnicmp(argv[0], gc_szCurrentUser, strlen(gc_szCurrentUser)) == 0)
  137. {
  138. pArgs->fUseLocalMachine = FALSE;
  139. }
  140. else
  141. {
  142. goto ErrorExit;
  143. }
  144. // increment to skip the backslash
  145. pszBreak++;
  146. if (ERROR_SUCCESS == AsciiToWideChar(pszBreak, &pArgs->pwszCertStore))
  147. {
  148. break;
  149. }
  150. }
  151. goto ErrorExit;
  152. case 's':
  153. argc--;
  154. argv++;
  155. if (argc <= 0 ||
  156. ERROR_SUCCESS != AsciiToWideChar(argv[0], &pArgs->pwszCertSubject))
  157. {
  158. goto ErrorExit;
  159. }
  160. break;
  161. case 'a':
  162. argc--;
  163. argv++;
  164. if (argc > 0)
  165. {
  166. pArgs->pszAccount = strchr(argv[0], '\\');
  167. if (pArgs->pszAccount)
  168. {
  169. // Break the two apart by removing the slash
  170. *(pArgs->pszAccount) = '\0';
  171. pArgs->pszAccount++;
  172. pArgs->pszDomain = argv[0];
  173. }
  174. else
  175. {
  176. pArgs->pszAccount = argv[0];
  177. }
  178. }
  179. else
  180. {
  181. goto ErrorExit;
  182. }
  183. break;
  184. case 'p':
  185. argc--;
  186. argv++;
  187. if (argc <= 0 ||
  188. ERROR_SUCCESS != AsciiToWideChar(argv[0], &pArgs->pwszPFXPassword))
  189. {
  190. // error: no password specified
  191. goto ErrorExit;
  192. }
  193. break;
  194. default:
  195. goto ErrorExit;
  196. }
  197. }
  198. // Now verify that we've obtained all the needed
  199. // options based on the desired config operation.
  200. if (pArgs->Command != ARGS_HELP)
  201. {
  202. // All options need -c.
  203. // All but -i need -s, and all but -i prohibit -p.
  204. // All but -l need -a.
  205. // -a not allowed for -l.
  206. // -s not allowed for -i.
  207. if (pArgs->pwszCertStore == NULL ||
  208. (pArgs->Command != ARGS_IMPORT_PFX &&
  209. (pArgs->pwszCertSubject == NULL || pArgs->pwszPFXPassword)) ||
  210. (pArgs->Command == ARGS_LIST_PRIVATE_KEY_ACCESS && pArgs->pszAccount) ||
  211. (pArgs->Command == ARGS_IMPORT_PFX && pArgs->pwszCertSubject))
  212. {
  213. // Incorrect set of options were passed, so flip this to
  214. // displaying the usage.
  215. goto ErrorExit;
  216. }
  217. }
  218. return;
  219. ErrorExit:
  220. pArgs->Command = ARGS_HELP;
  221. return;
  222. }
  223. //
  224. // AsciiToWideChar was borrowed from WinHttp\v5\common\util.cxx
  225. //
  226. DWORD
  227. AsciiToWideChar(const char * pszA, LPWSTR * ppszW)
  228. {
  229. DWORD cchA;
  230. DWORD cchW;
  231. *ppszW = NULL;
  232. if (!pszA)
  233. return ERROR_SUCCESS;
  234. cchA = lstrlenA(pszA);
  235. // Determine how big the widechar string will be
  236. cchW = MultiByteToWideChar(CP_ACP, 0, pszA, cchA, NULL, 0);
  237. *ppszW = new WCHAR[cchW+1];
  238. if (!*ppszW)
  239. return ERROR_NOT_ENOUGH_MEMORY;
  240. cchW = MultiByteToWideChar(CP_ACP, 0, pszA, cchA, *ppszW, cchW);
  241. (*ppszW)[cchW] = 0;
  242. return ERROR_SUCCESS;
  243. }
  244. DWORD
  245. ImportPFXFile(ARGS *pArgs)
  246. {
  247. CRYPTUI_WIZ_IMPORT_SRC_INFO importSrc;
  248. HCERTSTORE hDestCertStore = NULL;
  249. HCERTSTORE hTempCertStore = NULL;
  250. DWORD dwError = ERROR_SUCCESS;
  251. HINSTANCE hCryptUILib = NULL;
  252. CRYPTUIWIZIMPORT pfnCryptUIWizImport;
  253. hCryptUILib = LoadLibrary(TEXT("cryptui.dll"));
  254. if (hCryptUILib == NULL)
  255. {
  256. dwError = GetLastError();
  257. fprintf(stderr,
  258. "Error: Failed to load cryptography component with error code 0x%X\n",
  259. dwError);
  260. goto Cleanup;
  261. }
  262. pfnCryptUIWizImport = (CRYPTUIWIZIMPORT)GetProcAddress(hCryptUILib, "CryptUIWizImport");
  263. if (pfnCryptUIWizImport == NULL)
  264. {
  265. dwError = GetLastError();
  266. fprintf(stderr, "Error: Could not find needed DLL entry point\n");
  267. goto Cleanup;
  268. }
  269. hDestCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM,
  270. 0,
  271. NULL,
  272. CERT_STORE_OPEN_EXISTING_FLAG |
  273. (pArgs->fUseLocalMachine ? CERT_SYSTEM_STORE_LOCAL_MACHINE :
  274. CERT_SYSTEM_STORE_CURRENT_USER),
  275. pArgs->pwszCertStore);
  276. hTempCertStore = CertOpenStore(CERT_STORE_PROV_MEMORY,
  277. 0,
  278. NULL,
  279. 0,
  280. NULL);
  281. if (!hDestCertStore || !hTempCertStore)
  282. {
  283. dwError = GetLastError();
  284. fprintf(stderr,
  285. "Error: Failed to open certificate store with error code 0x%X\n",
  286. dwError);
  287. goto Cleanup;
  288. }
  289. // Ensure that it's really a PFX file that can be imported.
  290. if(CryptQueryObject(CERT_QUERY_OBJECT_FILE,
  291. pArgs->pwszPFXFile,
  292. CERT_QUERY_CONTENT_FLAG_PFX,
  293. CERT_QUERY_FORMAT_FLAG_ALL,
  294. 0,
  295. NULL,
  296. NULL,
  297. NULL,
  298. NULL,
  299. NULL,
  300. NULL))
  301. {
  302. memset(&importSrc, 0, sizeof(CRYPTUI_WIZ_IMPORT_SRC_INFO));
  303. importSrc.dwSize = sizeof(CRYPTUI_WIZ_IMPORT_SRC_INFO);
  304. importSrc.dwSubjectChoice = CRYPTUI_WIZ_IMPORT_SUBJECT_FILE;
  305. importSrc.pwszFileName = pArgs->pwszPFXFile;
  306. importSrc.pwszPassword = pArgs->pwszPFXPassword;
  307. if ((*pfnCryptUIWizImport)(CRYPTUI_WIZ_NO_UI |
  308. (pArgs->fUseLocalMachine ? CRYPTUI_WIZ_IMPORT_TO_LOCALMACHINE :
  309. CRYPTUI_WIZ_IMPORT_TO_CURRENTUSER) |
  310. CRYPTUI_WIZ_IMPORT_ALLOW_CERT,
  311. NULL,
  312. NULL,
  313. &importSrc,
  314. hTempCertStore))
  315. {
  316. DWORD dwImportCount = 0;
  317. DWORD dwAclCount = 0;
  318. BYTE *pSid = NULL;
  319. LPSTR pszDomain = NULL;
  320. dwError = GetSidForAccount(pArgs->pszDomain,
  321. pArgs->pszAccount,
  322. &pSid,
  323. &pszDomain);
  324. if (ERROR_SUCCESS != dwError)
  325. {
  326. goto Cleanup;
  327. }
  328. // Extract cert context(s) from temporary store and copy to
  329. // destination store. Need to enumerate and import one
  330. // at a time, so the private key access can also be modified.
  331. PCCERT_CONTEXT pCertContext = NULL;
  332. PCCERT_CONTEXT pDestCertContext = NULL;
  333. importSrc.dwSubjectChoice = CRYPTUI_WIZ_IMPORT_SUBJECT_CERT_CONTEXT;
  334. while (ERROR_SUCCESS == dwError &&
  335. (pCertContext = CertEnumCertificatesInStore(hTempCertStore, pCertContext)) != NULL)
  336. {
  337. // Root cert is last...break and add it to the root store.
  338. if (CheckForRootCert(pCertContext))
  339. break;
  340. // Overwrite if already installed.
  341. if (CertAddCertificateContextToStore(hDestCertStore,
  342. pCertContext,
  343. CERT_STORE_ADD_REPLACE_EXISTING,
  344. &pDestCertContext))
  345. {
  346. // Let the user see info regarding the cert imported
  347. fprintf(stdout, "Imported certificate:\n");
  348. DumpCertInfo(pCertContext);
  349. fprintf(stdout, "\n");
  350. dwError = ProcessCertContext(pDestCertContext,
  351. pSid,
  352. ARGS_ADD_PRIVATE_KEY_ACCESS);
  353. if (ERROR_SUCCESS == dwError)
  354. {
  355. dwAclCount++;
  356. }
  357. CertFreeCertificateContext(pDestCertContext);
  358. dwImportCount++;
  359. }
  360. else
  361. {
  362. fprintf(stderr,
  363. "Error: Failed to import a certificate, error = 0x%X\n\n",
  364. GetLastError());
  365. }
  366. }
  367. if (pCertContext)
  368. {
  369. CertCloseStore(hDestCertStore, 0);
  370. hDestCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM,
  371. 0,
  372. NULL,
  373. CERT_STORE_OPEN_EXISTING_FLAG |
  374. (pArgs->fUseLocalMachine ? CERT_SYSTEM_STORE_LOCAL_MACHINE :
  375. CERT_SYSTEM_STORE_CURRENT_USER),
  376. L"Root");
  377. if (!hDestCertStore)
  378. {
  379. dwError = GetLastError();
  380. fprintf(stderr, "Error: Unable to import root certificate\n\n");
  381. goto Cleanup;
  382. }
  383. if (!CertAddCertificateContextToStore(hDestCertStore,
  384. pCertContext,
  385. CERT_STORE_ADD_REPLACE_EXISTING,
  386. NULL))
  387. {
  388. fprintf(stderr, "Warning: Failed to import root certificate\n\n");
  389. }
  390. CertFreeCertificateContext(pCertContext);
  391. }
  392. if (pSid)
  393. LocalFree(pSid);
  394. if (pszDomain)
  395. LocalFree(pszDomain);
  396. }
  397. else
  398. {
  399. dwError = GetLastError();
  400. fprintf(stderr,
  401. "Error: Unable to import contents of PFX file.\n"
  402. " Please make sure the filename and path,\n"
  403. " as well as the password, are correct.\n\n",
  404. dwError);
  405. }
  406. }
  407. else
  408. {
  409. dwError = GetLastError();
  410. if (dwError == ERROR_FILE_NOT_FOUND)
  411. {
  412. fprintf(stderr,"Error: PFX file was not found\n");
  413. }
  414. else
  415. {
  416. fprintf(stderr,"Error: Unable to open PFX file\n");
  417. }
  418. }
  419. Cleanup:
  420. if (hTempCertStore)
  421. CertCloseStore(hTempCertStore, CERT_CLOSE_STORE_FORCE_FLAG);
  422. if (hDestCertStore)
  423. CertCloseStore(hDestCertStore, CERT_CLOSE_STORE_FORCE_FLAG);
  424. if (hCryptUILib)
  425. {
  426. FreeLibrary(hCryptUILib);
  427. }
  428. return dwError;
  429. }
  430. VOID
  431. DumpSid(BYTE *pSid)
  432. {
  433. LPTSTR pszDomain = NULL;
  434. LPTSTR pszAccount = NULL;
  435. if (ERROR_SUCCESS == GetAccountForSid(pSid, &pszDomain, &pszAccount))
  436. {
  437. if (pszDomain)
  438. {
  439. fprintf(stdout,
  440. " %s\\%s\n",
  441. pszDomain,
  442. pszAccount);
  443. }
  444. else
  445. {
  446. fprintf(stdout,
  447. " %s\n",
  448. pszDomain,
  449. pszAccount);
  450. }
  451. if (pszDomain)
  452. LocalFree(pszDomain);
  453. if (pszAccount)
  454. LocalFree(pszAccount);
  455. }
  456. }
  457. DWORD
  458. GetAccountForSid(BYTE *pSid,
  459. LPTSTR *ppszDomain,
  460. LPTSTR *ppszAccount)
  461. {
  462. DWORD cbAccountLength = 0;
  463. DWORD cbDomainLength = 0;
  464. SID_NAME_USE eUse;
  465. DWORD dwError = ERROR_SUCCESS;
  466. if (!pSid || !ppszDomain || !ppszAccount || !IsValidSid(pSid))
  467. return ERROR_INVALID_PARAMETER;
  468. *ppszDomain = NULL;
  469. *ppszAccount = NULL;
  470. if (!LookupAccountSid(NULL,
  471. pSid,
  472. *ppszAccount,
  473. &cbAccountLength,
  474. *ppszDomain,
  475. &cbDomainLength,
  476. &eUse))
  477. {
  478. if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
  479. {
  480. return GetLastError();
  481. }
  482. }
  483. else
  484. {
  485. return ERROR_INVALID_PARAMETER;
  486. }
  487. if (cbDomainLength)
  488. {
  489. *ppszDomain = (LPTSTR) LocalAlloc(LPTR, cbDomainLength * sizeof(TCHAR));
  490. if (!*ppszDomain)
  491. {
  492. dwError = ERROR_NOT_ENOUGH_MEMORY;
  493. goto ErrorExit;
  494. };
  495. }
  496. *ppszAccount = (LPTSTR) LocalAlloc(LPTR, cbAccountLength * sizeof(TCHAR));
  497. if (!*ppszAccount)
  498. {
  499. dwError = ERROR_NOT_ENOUGH_MEMORY;
  500. goto ErrorExit;
  501. }
  502. if (!LookupAccountSid(NULL,
  503. pSid,
  504. *ppszAccount,
  505. &cbAccountLength,
  506. *ppszDomain,
  507. &cbDomainLength,
  508. &eUse))
  509. {
  510. dwError = GetLastError();
  511. goto ErrorExit;
  512. }
  513. // Made it through, should be ERROR_SUCCESS
  514. return dwError;
  515. ErrorExit:
  516. if (*ppszDomain)
  517. {
  518. LocalFree(*ppszDomain);
  519. *ppszDomain = NULL;
  520. }
  521. if (*ppszAccount)
  522. {
  523. LocalFree(*ppszAccount);
  524. *ppszAccount = NULL;
  525. }
  526. fprintf(stderr,"Error: Unable to determine account name for SID, error = 0x%X\n", dwError);
  527. return dwError;
  528. }
  529. DWORD
  530. GetSidForAccount(LPCTSTR pszDomain,
  531. LPCTSTR pszAccount,
  532. BYTE **ppSid,
  533. char **ppszDomain)
  534. {
  535. SID_NAME_USE su;
  536. DWORD cbSidLength = 0;
  537. DWORD cbDomainLength = 0;
  538. DWORD dwError = ERROR_SUCCESS;
  539. if (!pszAccount || !ppSid || !ppszDomain)
  540. return ERROR_INVALID_PARAMETER;
  541. *ppSid = NULL;
  542. *ppszDomain = NULL;
  543. // Determine the SID that belongs to the user. Make first call to
  544. // get the needed buffer sizes.
  545. if (!(LookupAccountName(pszDomain,
  546. pszAccount,
  547. *ppSid,
  548. &cbSidLength,
  549. *ppszDomain,
  550. &cbDomainLength,
  551. &su)))
  552. {
  553. if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
  554. {
  555. dwError = GetLastError();
  556. goto ErrorExit;
  557. }
  558. }
  559. else
  560. {
  561. return ERROR_INVALID_PARAMETER;
  562. }
  563. *ppSid = (BYTE *) LocalAlloc(LPTR, cbSidLength);
  564. if (!*ppSid)
  565. {
  566. return ERROR_NOT_ENOUGH_MEMORY;
  567. };
  568. *ppszDomain = (char *) LocalAlloc(LPTR, cbDomainLength);
  569. if (!*ppszDomain)
  570. {
  571. dwError = ERROR_NOT_ENOUGH_MEMORY;
  572. goto ErrorExit;
  573. }
  574. if (!LookupAccountName(pszDomain,
  575. pszAccount,
  576. *ppSid,
  577. &cbSidLength,
  578. *ppszDomain,
  579. &cbDomainLength,
  580. &su))
  581. {
  582. dwError = ERROR_NOT_ENOUGH_MEMORY;
  583. goto ErrorExit;
  584. }
  585. // Double check the returned SID
  586. if (!IsValidSid(*ppSid))
  587. {
  588. dwError = GetLastError();
  589. goto ErrorExit;
  590. }
  591. return dwError;
  592. ErrorExit:
  593. if (*ppSid)
  594. {
  595. LocalFree(*ppSid);
  596. *ppSid = NULL;
  597. }
  598. if (*ppszDomain)
  599. {
  600. LocalFree(*ppszDomain);
  601. *ppszDomain = NULL;
  602. }
  603. fprintf(stderr, "Error: No account information was found.\n", dwError);
  604. return dwError;
  605. }
  606. DWORD
  607. DumpAccessAllowedList(PACL pDacl)
  608. {
  609. WORD wIndex;
  610. LPVOID pAce = NULL;
  611. if (pDacl == NULL)
  612. {
  613. fprintf(stdout, "The Discretionary Access Control List (DACL) for this object is a NULL DACL. "
  614. "This implies everyone has full access to this object. This NULL DACL could be"
  615. "in place because the system is running on a filesystem which does not support "
  616. "protected files (such as the FAT32 filesystem)"
  617. "\n");
  618. goto done;
  619. }
  620. fprintf(stdout, "Additional accounts and groups with access to the private key include:\n");
  621. for (wIndex = 0; wIndex < pDacl->AceCount; wIndex++)
  622. {
  623. if (GetAce(pDacl, wIndex, &pAce))
  624. {
  625. // Should only be an access allowed ace, right?
  626. if (((ACCESS_ALLOWED_ACE *)pAce)->Header.AceType ==
  627. ACCESS_ALLOWED_ACE_TYPE)
  628. {
  629. DumpSid((BYTE *)&(((ACCESS_ALLOWED_ACE *)pAce)->SidStart));
  630. }
  631. }
  632. }
  633. done:
  634. return ERROR_SUCCESS;
  635. }
  636. // Wrapper that performs common work around the list, add, and remove actions
  637. // involving a private key for a specified certificate.
  638. DWORD DoPrivateKeyAccessAction(ARGS *pArgs)
  639. {
  640. HCRYPTPROV hProv = NULL;
  641. BOOL fFreeProv = FALSE;
  642. HCERTSTORE hCertStore = NULL;
  643. PCCERT_CONTEXT pCertContext = NULL;
  644. DWORD dwError = ERROR_SUCCESS;
  645. PACL pDacl = NULL;
  646. DWORD cbSD = 0;
  647. // Assume args have already been verified.
  648. hCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM,
  649. 0,
  650. NULL,
  651. CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG |
  652. (pArgs->fUseLocalMachine ? CERT_SYSTEM_STORE_LOCAL_MACHINE :
  653. CERT_SYSTEM_STORE_CURRENT_USER),
  654. pArgs->pwszCertStore);
  655. if (!hCertStore)
  656. {
  657. DWORD dwError = GetLastError();
  658. goto Cleanup;
  659. }
  660. pCertContext = CertFindCertificateInStore(hCertStore,
  661. X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
  662. 0,
  663. CERT_FIND_SUBJECT_STR,
  664. (LPVOID) pArgs->pwszCertSubject,
  665. NULL);
  666. if (pCertContext)
  667. {
  668. BYTE *pSid = NULL;
  669. LPSTR pszDomain = NULL;
  670. // Let the user have a solid idea of which cert was matched.
  671. fprintf(stdout, "Matching certificate:\n");
  672. DumpCertInfo(pCertContext);
  673. // Bogus command will fall through when processing the cert context.
  674. if (pArgs->Command != ARGS_LIST_PRIVATE_KEY_ACCESS)
  675. {
  676. dwError = GetSidForAccount(pArgs->pszDomain,
  677. pArgs->pszAccount,
  678. &pSid,
  679. &pszDomain);
  680. }
  681. if (ERROR_SUCCESS == dwError)
  682. {
  683. dwError = ProcessCertContext(pCertContext, pSid, pArgs->Command);
  684. if (pSid)
  685. LocalFree(pSid);
  686. if (pszDomain)
  687. LocalFree(pszDomain);
  688. }
  689. }
  690. else
  691. {
  692. fprintf(stderr, "Error: Unable to find or obtain a context for requested certificate\n\n");
  693. dwError = ERROR_NOT_FOUND;
  694. }
  695. Cleanup:
  696. if (pCertContext)
  697. {
  698. CertFreeCertificateContext(pCertContext);
  699. }
  700. if (hCertStore)
  701. {
  702. CertCloseStore(hCertStore, CERT_CLOSE_STORE_FORCE_FLAG);
  703. }
  704. return dwError;
  705. }
  706. // For a given cert, obtain the DACL associated with its private
  707. // key and process the appropriate command.
  708. DWORD
  709. ProcessCertContext(PCCERT_CONTEXT pCertContext, BYTE *pSid, ARGTYPE eCommand)
  710. {
  711. DWORD dwError = ERROR_SUCCESS;
  712. DWORD dwKeySpec;
  713. DWORD cbSD;
  714. WORD wIndex;
  715. LPVOID pAce = NULL;
  716. PSECURITY_DESCRIPTOR pSD = NULL;
  717. SECURITY_DESCRIPTOR sd;
  718. HCRYPTPROV hProv = NULL;
  719. BOOL fFreeProv = FALSE;
  720. PACL pDacl = NULL;
  721. PACL pNewDacl = NULL;
  722. BOOL fPresent = FALSE;
  723. BOOL fDefault = FALSE;
  724. if (!pCertContext)
  725. return ERROR_INVALID_PARAMETER;
  726. // Get the CSP for the cert context.
  727. // The client must be the owner to do this.
  728. if (CryptAcquireCertificatePrivateKey(pCertContext,
  729. CRYPT_ACQUIRE_USE_PROV_INFO_FLAG |
  730. CRYPT_ACQUIRE_SILENT_FLAG,
  731. NULL,
  732. &hProv,
  733. &dwKeySpec,
  734. &fFreeProv))
  735. {
  736. // Grab the DACL ACE list
  737. if (CryptGetProvParam(hProv,
  738. PP_KEYSET_SEC_DESCR,
  739. NULL,
  740. &cbSD,
  741. DACL_SECURITY_INFORMATION))
  742. {
  743. pSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, cbSD);
  744. if (pSD)
  745. {
  746. if (CryptGetProvParam(hProv,
  747. PP_KEYSET_SEC_DESCR,
  748. (BYTE *)pSD,
  749. &cbSD,
  750. DACL_SECURITY_INFORMATION))
  751. {
  752. if (!GetSecurityDescriptorDacl(pSD,
  753. &fPresent,
  754. &pDacl,
  755. &fDefault) && fPresent)
  756. {
  757. dwError = GetLastError();
  758. fprintf(stderr, "Error: Failed to obtain access list private key\n\n");
  759. goto Cleanup;
  760. }
  761. }
  762. else
  763. {
  764. dwError = GetLastError();
  765. fprintf(stderr, "Error: Failed to obtain security information for private key\n\n");
  766. goto Cleanup;
  767. }
  768. }
  769. else
  770. {
  771. dwError = ERROR_NOT_ENOUGH_MEMORY;
  772. goto Cleanup;
  773. }
  774. }
  775. else
  776. {
  777. fprintf(stderr, "Error: Failed to obtain security descriptor for private key\n\n");
  778. goto Cleanup;
  779. }
  780. }
  781. else
  782. {
  783. fprintf(stderr, "Error: Access was not successfully obtained for the private key.\n");
  784. fprintf(stderr, " This can only be done by the user who installed the certificate.\n\n");
  785. goto Cleanup;
  786. }
  787. switch (eCommand)
  788. {
  789. case ARGS_ADD_PRIVATE_KEY_ACCESS:
  790. case ARGS_REMOVE_PRIVATE_KEY_ACCESS:
  791. if (pDacl == NULL)
  792. {
  793. fprintf(stdout, "OPERATION FAILED\n"
  794. "The Discretionary Access Control List (DACL) for this object is a NULL DACL. "
  795. "This implies everyone has full access to this object. This NULL DACL could be"
  796. "in place because the system is running on a filesystem which does not support "
  797. "protected files (such as the FAT32 filesystem)"
  798. "\n");
  799. dwError = ERROR_SUCCESS;
  800. goto Cleanup;
  801. }
  802. if (eCommand == ARGS_ADD_PRIVATE_KEY_ACCESS)
  803. dwError = AddPrivateKeyAccess(pDacl, pSid, &pNewDacl);
  804. else
  805. dwError = RemovePrivateKeyAccess(pDacl, pSid);
  806. // If successful, update the DACL in the security descriptor
  807. if (ERROR_SUCCESS == dwError)
  808. {
  809. SECURITY_DESCRIPTOR_CONTROL sdControl;
  810. DWORD dwRevision;
  811. if (!GetSecurityDescriptorControl(pSD, &sdControl, &dwRevision))
  812. {
  813. dwError = GetLastError();
  814. goto Cleanup;
  815. }
  816. // initialize a new security descriptor.
  817. if(!InitializeSecurityDescriptor(&sd, dwRevision))
  818. {
  819. dwError = GetLastError();
  820. goto Cleanup;
  821. }
  822. // add the ACL to the security descriptor.
  823. if(!SetSecurityDescriptorDacl(&sd,
  824. TRUE,
  825. pNewDacl ? pNewDacl : pDacl,
  826. FALSE))
  827. {
  828. dwError = GetLastError();
  829. goto Cleanup;
  830. }
  831. // Set descriptor control.
  832. // API Helper for this exists only on Win2K and up.
  833. MySetSecurityDescriptorControl(&sd, SE_DACL_PROTECTED, SE_DACL_PROTECTED);
  834. if (!IsValidSecurityDescriptor(&sd))
  835. {
  836. dwError = ERROR_INVALID_PARAMETER;
  837. goto Cleanup;
  838. }
  839. if(!CryptSetProvParam(hProv,
  840. PP_KEYSET_SEC_DESCR,
  841. (BYTE*)&sd,
  842. DACL_SECURITY_INFORMATION))
  843. {
  844. dwError = GetLastError();
  845. goto Cleanup;
  846. }
  847. }
  848. break;
  849. case ARGS_LIST_PRIVATE_KEY_ACCESS:
  850. DumpAccessAllowedList(pDacl);
  851. // Don't care about reporting an error here when dumping the list.
  852. dwError = ERROR_SUCCESS;
  853. break;
  854. default:
  855. // should never be here...do nothing
  856. break;
  857. }
  858. Cleanup:
  859. if (ERROR_SUCCESS != dwError)
  860. {
  861. fprintf(stderr,
  862. "Error: Unable to update security info for key container, error = 0x%X\n\n",
  863. dwError);
  864. }
  865. if (fFreeProv && hProv)
  866. {
  867. CryptReleaseContext(hProv, 0);
  868. }
  869. if (pSD)
  870. {
  871. LocalFree(pSD);
  872. }
  873. return dwError;
  874. }
  875. DWORD
  876. AddPrivateKeyAccess(PACL pDacl, BYTE *pSid, PACL *ppNewDacl)
  877. {
  878. WORD wIndex;
  879. BOOL fFound = FALSE;
  880. LPVOID pAce = NULL;
  881. for (wIndex = 0; wIndex < pDacl->AceCount; wIndex++)
  882. {
  883. if (GetAce(pDacl, wIndex, &pAce))
  884. {
  885. // Should only be an access allowed ace, right?
  886. if (((ACCESS_ALLOWED_ACE *)pAce)->Header.AceType ==
  887. ACCESS_ALLOWED_ACE_TYPE)
  888. {
  889. if (EqualSid((BYTE *)&(((ACCESS_ALLOWED_ACE *)pAce)->SidStart), pSid))
  890. {
  891. fprintf(stdout, "Private key access has already been granted for account:\n");
  892. DumpSid((BYTE *)&(((ACCESS_ALLOWED_ACE *)pAce)->SidStart));
  893. fFound = TRUE;
  894. }
  895. }
  896. }
  897. }
  898. DWORD dwError = ERROR_SUCCESS;
  899. if (!fFound)
  900. {
  901. fprintf(stdout, "Granting private key access for account:\n");
  902. DumpSid(pSid);
  903. if (!AddAccessAllowedAce(pDacl, ACL_REVISION, KEY_ALL_ACCESS, pSid))
  904. {
  905. dwError = GetLastError();
  906. if (ERROR_ALLOTTED_SPACE_EXCEEDED == dwError)
  907. {
  908. // Not enough space, so allocate a new dacl,
  909. // and copy data from the old acl and append new ace.
  910. WORD wAclSize = pDacl->AclSize + (WORD)GetLengthSid(pSid) +
  911. sizeof(ACCESS_ALLOWED_OBJECT_ACE);
  912. dwError = ERROR_SUCCESS;
  913. // Allocate dacl + sizeof new sid + sizeof largest ace
  914. *ppNewDacl = (PACL) LocalAlloc(LPTR, wAclSize);
  915. if (NULL == *ppNewDacl)
  916. {
  917. dwError = ERROR_NOT_ENOUGH_MEMORY;
  918. goto ErrorExit;
  919. }
  920. if (!InitializeAcl (*ppNewDacl, wAclSize, pDacl->AclRevision))
  921. {
  922. dwError = GetLastError();
  923. LocalFree(*ppNewDacl);
  924. *ppNewDacl = NULL;
  925. goto ErrorExit;
  926. }
  927. // Copy all the original ACEs
  928. for (wIndex = 0; wIndex < pDacl->AceCount; wIndex++)
  929. {
  930. if (GetAce(pDacl, wIndex, &pAce))
  931. {
  932. if (((ACCESS_ALLOWED_ACE *)pAce)->Header.AceType ==
  933. ACCESS_ALLOWED_ACE_TYPE)
  934. {
  935. if (!AddAccessAllowedAce(*ppNewDacl,
  936. ACL_REVISION,
  937. ((ACCESS_ALLOWED_ACE *)pAce)->Mask,
  938. (BYTE *)&(((ACCESS_ALLOWED_ACE *)pAce)->SidStart)))
  939. {
  940. dwError = GetLastError();
  941. break;
  942. }
  943. }
  944. else
  945. {
  946. // Should only contain access allowed ACEs,
  947. // but we'll handle anyway.
  948. if (!AddAccessDeniedAce(*ppNewDacl,
  949. ACL_REVISION,
  950. ((ACCESS_DENIED_ACE *)pAce)->Mask,
  951. (BYTE *)&(((ACCESS_DENIED_ACE *)pAce)->SidStart)))
  952. {
  953. dwError = GetLastError();
  954. break;
  955. }
  956. }
  957. }
  958. else
  959. {
  960. dwError = GetLastError();
  961. break;
  962. }
  963. }
  964. // Check for any errors
  965. if (wIndex >= pDacl->AceCount)
  966. {
  967. // Try again
  968. if (!AddAccessAllowedAce(*ppNewDacl, ACL_REVISION, KEY_ALL_ACCESS, pSid))
  969. {
  970. dwError = GetLastError();
  971. }
  972. }
  973. else
  974. {
  975. LocalFree(*ppNewDacl);
  976. *ppNewDacl = NULL;
  977. }
  978. }
  979. }
  980. }
  981. ErrorExit:
  982. if (ERROR_SUCCESS != dwError)
  983. {
  984. fprintf(stderr, "Error: Failed to grant access with error code 0x%X\n\n", dwError);
  985. }
  986. return dwError;
  987. }
  988. DWORD
  989. RemovePrivateKeyAccess(PACL pDacl, BYTE *pSid)
  990. {
  991. WORD wIndex;
  992. LPVOID pAce = NULL;
  993. BOOL fFound = FALSE;
  994. for (wIndex = 0; wIndex < pDacl->AceCount; wIndex++)
  995. {
  996. if (GetAce(pDacl, wIndex, &pAce))
  997. {
  998. // Should only be an access allowed ace, right?
  999. if (((ACCESS_ALLOWED_ACE *)pAce)->Header.AceType ==
  1000. ACCESS_ALLOWED_ACE_TYPE)
  1001. {
  1002. if (EqualSid((BYTE *)&(((ACCESS_ALLOWED_ACE *)pAce)->SidStart), pSid))
  1003. {
  1004. fprintf(stdout, "Removing private key access for account:\n");
  1005. DumpSid((BYTE *)&(((ACCESS_ALLOWED_ACE *)pAce)->SidStart));
  1006. if (!DeleteAce(pDacl, wIndex))
  1007. {
  1008. // Break on error
  1009. DWORD dwError = GetLastError();
  1010. fprintf(stderr,
  1011. "Error: Failed to remove access with error code 0x%X\n\n",
  1012. dwError);
  1013. return dwError;
  1014. }
  1015. fFound = TRUE; // Keep going? Can there be dupes?
  1016. }
  1017. }
  1018. }
  1019. }
  1020. if (!fFound)
  1021. {
  1022. fprintf(stderr, "Account already does not have access to private key.\n\n");
  1023. }
  1024. return ERROR_SUCCESS;
  1025. }
  1026. // Wrapper to only set on Win2K and up because the API
  1027. // doesn't exist on NT4
  1028. BOOL
  1029. MySetSecurityDescriptorControl(
  1030. PSECURITY_DESCRIPTOR pSD,
  1031. SECURITY_DESCRIPTOR_CONTROL ControlBitsOfInterest,
  1032. SECURITY_DESCRIPTOR_CONTROL ControlBitsToSet
  1033. )
  1034. {
  1035. SETSECURITYDESCRIPTORCONTROL pfnSetSecurityDescriptorControl = NULL;
  1036. HMODULE hModule = NULL;
  1037. hModule = GetModuleHandle("advapi32.dll");
  1038. if (NULL != hModule)
  1039. {
  1040. pfnSetSecurityDescriptorControl = (SETSECURITYDESCRIPTORCONTROL)
  1041. GetProcAddress(hModule,
  1042. "SetSecurityDescriptorControl");
  1043. if (NULL != pfnSetSecurityDescriptorControl)
  1044. {
  1045. return (*pfnSetSecurityDescriptorControl)(pSD,
  1046. ControlBitsOfInterest,
  1047. ControlBitsToSet);
  1048. }
  1049. }
  1050. return FALSE;
  1051. }
  1052. VOID
  1053. DumpCertInfo(PCCERT_CONTEXT pCertContext)
  1054. {
  1055. LPTSTR pszCertName = NULL;
  1056. DWORD cbCertName = 0;
  1057. if (!pCertContext)
  1058. return;
  1059. cbCertName = CertNameToStr(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
  1060. &pCertContext->pCertInfo->Subject,
  1061. CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG,
  1062. NULL,
  1063. 0);
  1064. pszCertName = (LPTSTR) LocalAlloc(LPTR, cbCertName * sizeof(TCHAR) + 1);
  1065. if (!pszCertName)
  1066. return;
  1067. *pszCertName = TEXT('\0');
  1068. if (CertNameToStr(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
  1069. &pCertContext->pCertInfo->Subject,
  1070. CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG | CERT_NAME_STR_REVERSE_FLAG,
  1071. pszCertName,
  1072. cbCertName))
  1073. {
  1074. fprintf(stdout, "%s\n\n", pszCertName);
  1075. }
  1076. }
  1077. // Does quick and dirty check for a root cert by testing
  1078. // the issuer and issued to fields.
  1079. BOOL
  1080. CheckForRootCert(PCCERT_CONTEXT pCertContext)
  1081. {
  1082. TCHAR szIssuer[1024] = TEXT("");
  1083. TCHAR szIssuedTo[1024] = TEXT("");
  1084. DWORD cbCertName = 1024;
  1085. if (!pCertContext)
  1086. return FALSE;
  1087. CertGetNameString(pCertContext,
  1088. CERT_NAME_SIMPLE_DISPLAY_TYPE,
  1089. CERT_NAME_ISSUER_FLAG,
  1090. NULL,
  1091. szIssuer,
  1092. cbCertName);
  1093. CertGetNameString(pCertContext,
  1094. CERT_NAME_SIMPLE_DISPLAY_TYPE,
  1095. 0,
  1096. NULL,
  1097. szIssuedTo,
  1098. cbCertName);
  1099. return (lstrcmp(szIssuer, szIssuedTo) == 0) ? TRUE : FALSE;
  1100. }
  1101. int __cdecl main (int argc, char **argv)
  1102. {
  1103. ARGS Args;
  1104. DWORD dwErr = ERROR_SUCCESS;
  1105. fprintf (stdout,
  1106. "Microsoft (R) WinHTTP Certificate Configuration Tool\n"
  1107. "Copyright (C) Microsoft Corporation 2001.\n\n"
  1108. );
  1109. // Discard program argument
  1110. argv++;
  1111. argc--;
  1112. ParseArguments(argc, argv, &Args);
  1113. switch (Args.Command)
  1114. {
  1115. case ARGS_IMPORT_PFX:
  1116. dwErr = ImportPFXFile(&Args);
  1117. break;
  1118. case ARGS_ADD_PRIVATE_KEY_ACCESS:
  1119. case ARGS_REMOVE_PRIVATE_KEY_ACCESS:
  1120. case ARGS_LIST_PRIVATE_KEY_ACCESS:
  1121. dwErr = DoPrivateKeyAccessAction(&Args);
  1122. break;
  1123. case ARGS_HELP:
  1124. default:
  1125. fprintf(stderr,
  1126. "Usage:\n\n"
  1127. " winhttpcertcfg [-?] : To view help information\n\n"
  1128. " winhttpcertcfg [-i PFXFile | -g | -r | -l]\n"
  1129. " [-a Account] [-c CertStore] [-s SubjectStr] [-p PFXPassword]\n\n"
  1130. "Note:\n\n"
  1131. " The user must have sufficient privileges to use this tool,\n"
  1132. " which likely requires the user to be an administrator and\n"
  1133. " the same user who installed the client certificate, if it is\n"
  1134. " already installed.\n\n"
  1135. "Options:\n\n"
  1136. " To list accounts which have access to the private key for\n"
  1137. " specified certificate:\n"
  1138. " winhttpcertcfg -l -c CertLocation -s SubjectStr\n\n"
  1139. " To grant access to private key for an account with\n"
  1140. " specified certificate that is already installed:\n"
  1141. " winhttpcertcfg -g -c CertLocation -s SubjectStr -a Account\n\n"
  1142. " To import a certificate plus private key from a PFX file:\n"
  1143. " winhttpcertcfg -i PFXFile -c CertLocation\n\n"
  1144. " To remove access to private key for an account with\n"
  1145. " specified certificate:\n"
  1146. " winhttpcertcfg -r -c CertLocation -s SubjectStr -a Account\n\n"
  1147. "Description of secondary options:\n\n"
  1148. " -c LOCAL_MACHINE|CURRENT_USER\\CertStore\n\n"
  1149. " Use LOCAL_MACHINE or CURRENT_USER to designate which\n"
  1150. " registry branch to use for the location. The\n"
  1151. " certificate store can be any installed on the machine.\n"
  1152. " Typical examples are MY, Root, and TrustedPeople.\n\n"
  1153. " -a Account\n\n"
  1154. " User account on the machine being configured. This could\n"
  1155. " be a local machine or domain account, such as:\n"
  1156. " IWAM_TESTMACHINE, TESTUSER, or TESTDOMAIN\\DOMAINUSER.\n\n"
  1157. " -s SubjectStr\n\n"
  1158. " Case-insensitive search string for finding the first\n"
  1159. " enumerated certificate with a subject name that contains\n"
  1160. " this substring.\n\n"
  1161. " -p PFXPassword\n\n"
  1162. " Password to use for importing the certificate and\n"
  1163. " private key. This option can only be used with -i.\n\n");
  1164. break;
  1165. }
  1166. if (Args.pwszPFXFile)
  1167. delete [] Args.pwszPFXFile;
  1168. if (Args.pwszCertStore)
  1169. delete [] Args.pwszCertStore;
  1170. if (Args.pwszCertSubject)
  1171. delete [] Args.pwszCertSubject;
  1172. if (Args.pwszPFXPassword)
  1173. delete [] Args.pwszPFXPassword;
  1174. return dwErr;
  1175. }