#include #include #include #include #include #include #include #define SHA1_HASH_LEN 20 #define MSG_ERROR_S 1 #define MSG_ERROR_C 2 #define MSG_WARNING_S 4 #define MSG_WARNING_C 8 #define MSG_INFO_S 16 #define MSG_INFO_C 32 #define szOID_WINDOWS_SYSTEM_COMPONENT_VERIFICATION "1.3.6.1.4.1.311.10.3.6" #define wszCERTTYPE_WINDOWS_TEST_BUILD_SIGNING L"WindowsTestBuildSigning" PWCHAR mySanitizeName(IN WCHAR const *pwszName); DWORD dwMsgLevel = MSG_ERROR_S | MSG_ERROR_C | MSG_INFO_S | MSG_INFO_C; #define PrintMessage(MsgType, Msg) \ if (MsgType & dwMsgLevel & MSG_ERROR_S) _PrintMessage(L"csenroll: error: "); \ else if (MsgType & dwMsgLevel & MSG_ERROR_C) _PrintMessage(L" "); \ else if (MsgType & dwMsgLevel & MSG_WARNING_S) _PrintMessage(L"csenroll: warning: "); \ else if (MsgType & dwMsgLevel & MSG_WARNING_C) _PrintMessage(L" "); \ else if (MsgType & dwMsgLevel & MSG_INFO_S) _PrintMessage(L"csenroll: "); \ else if (MsgType & dwMsgLevel & MSG_INFO_C) _PrintMessage(L" "); \ if (MsgType & dwMsgLevel) _PrintMessage Msg void _PrintMessage( LPWSTR pwszFormat, ... ) { WCHAR rgwszBuffer[1024]; va_list argList; va_start(argList, pwszFormat); vswprintf(rgwszBuffer, pwszFormat, argList); fwprintf(stderr, rgwszBuffer); } BOOL EnrollForCodeSigningCertificate( IN LPWSTR pwszCAName, IN LPWSTR pwszDNSName, PCERT_CONTEXT pOldCert ) { CRYPTUI_WIZ_CERT_REQUEST_INFO CertRequestInfo; CRYPTUI_WIZ_CERT_REQUEST_PVK_NEW NewKeyInfo; CRYPTUI_WIZ_CERT_TYPE CertType; CRYPT_KEY_PROV_INFO ProviderInfo; PCCERT_CONTEXT pCertContext = NULL; PCCERT_CONTEXT pCert = NULL; DWORD dwCAStatus; DWORD dwAcquireFlags = 0; LPWSTR pwszProvName = NULL; WCHAR rgwszMachineName[MAX_COMPUTERNAME_LENGTH + 1]; DWORD cMachineName = MAX_COMPUTERNAME_LENGTH + 1; CRYPT_DATA_BLOB CryptData; DWORD dwErr = 0; BOOL fRet = FALSE; LPWSTR rgwszCertType[2]; memset(&CertRequestInfo, 0, sizeof(CertRequestInfo)); memset(&NewKeyInfo, 0, sizeof(NewKeyInfo)); memset(&ProviderInfo, 0, sizeof(ProviderInfo)); memset(&rgwszMachineName, 0, sizeof(rgwszMachineName)); memset(&CryptData, 0, sizeof(CryptData)); memset(&CertType, 0, sizeof(CertType)); // set up the provider info ProviderInfo.dwProvType = 0; // pInfo->dwProvType; ProviderInfo.pwszProvName = NULL; // The wizard will choose one based // on the cert type // set the acquire context flags // UNDONE - need to add silent flag ProviderInfo.dwFlags = 0; // dwAcquireFlags; // set the key specification ProviderInfo.dwKeySpec = 0; // pInfo->dwKeySpec; // set up the new key info NewKeyInfo.dwSize = sizeof(NewKeyInfo); NewKeyInfo.pKeyProvInfo = &ProviderInfo; // set the flags to be passed when calling CryptGenKey NewKeyInfo.dwGenKeyFlags = 0; // pInfo->dwGenKeyFlags; // set the request info CertRequestInfo.dwSize = sizeof(CertRequestInfo); // cert exists then check if expired (if so do renewal) if (pOldCert) { CertRequestInfo.dwPurpose = CRYPTUI_WIZ_CERT_RENEW; CertRequestInfo.pRenewCertContext = pOldCert; } else { CertRequestInfo.dwPurpose = CRYPTUI_WIZ_CERT_ENROLL; CertRequestInfo.pRenewCertContext = NULL; } // UNDONE - for now always gen a new key, later may allow using existing key // for things like renewal CertRequestInfo.dwPvkChoice = CRYPTUI_WIZ_CERT_REQUEST_PVK_CHOICE_NEW; CertRequestInfo.pPvkNew = &NewKeyInfo; // destination cert store is the MY store (!!!! hard coded !!!!) CertRequestInfo.pwszDesStore = L"MY"; // set algorithm for hashing CertRequestInfo.pszHashAlg = NULL; // set the cert type rgwszCertType[0] = wszCERTTYPE_WINDOWS_TEST_BUILD_SIGNING; rgwszCertType[1] = NULL; CertRequestInfo.dwCertChoice = CRYPTUI_WIZ_CERT_REQUEST_CERT_TYPE; CertType.dwSize = sizeof(CertType); CertType.cCertType = 1; CertType.rgwszCertType = rgwszCertType; CertRequestInfo.pCertType = &CertType; // set the Cert Server machine and authority CertRequestInfo.pwszCALocation = pwszDNSName; CertRequestInfo.pwszCAName = mySanitizeName(pwszCAName); // certify and create a key at the same time if (!CryptUIWizCertRequest( CRYPTUI_WIZ_NO_UI, 0, NULL, &CertRequestInfo, &pCertContext, &dwCAStatus)) { PrintMessage(MSG_ERROR_S, (L"CyptUIWizCertRequest failed with %lxh\n", GetLastError())); goto Ret; } if (dwCAStatus != CRYPTUI_WIZ_CERT_REQUEST_STATUS_SUCCEEDED) { PrintMessage(MSG_ERROR_S, (L"CyptUIWizCertRequest failed to issue certificate\n")); goto Ret; } fRet = TRUE; Ret: if (CertRequestInfo.pwszCAName) LocalFree((PVOID) CertRequestInfo.pwszCAName); if (pCertContext) CertFreeCertificateContext(pCertContext); if (pCert) CertFreeCertificateContext(pCert); if (pwszProvName) LocalFree(pwszProvName); return fRet; } BOOL ExportCertificateHash( PCERT_CONTEXT pCertContext ) { BYTE bSHA1Hash[SHA1_HASH_LEN], bPrintHash[SHA1_HASH_LEN * 2 + 1]; DWORD cbHashLen = sizeof(bSHA1Hash), i; if (CertGetCertificateContextProperty( pCertContext, CERT_SHA1_HASH_PROP_ID, bSHA1Hash, &cbHashLen ) == FALSE) { return FALSE; } for (i = 0; i < SHA1_HASH_LEN; i++) { BYTE dwVal = bSHA1Hash[i]; bPrintHash[i * 2] = ((dwVal >> 4) >= 10 ? (dwVal >> 4) + 'A' - 10 : (dwVal >> 4) + '0'); bPrintHash[i * 2 + 1] = ((dwVal & 0xf) >= 10 ? (dwVal & 0xf) + 'A' - 10 : (dwVal & 0xf) + '0'); } bPrintHash[SHA1_HASH_LEN * 2] = '\0'; printf(bPrintHash); return TRUE; } PCERT_CONTEXT FindCodeSigningCertificate( HCERTSTORE hCertStore, LPWSTR pwszCAName, BYTE *pbSHA1Hash ) { PCERT_CONTEXT pRootContext = NULL, pCertContext = NULL; PCERT_CONTEXT pPrevCertContext = NULL, pReturnCertContext = NULL; PCERT_CHAIN_CONTEXT pChainContext = NULL; CERT_CHAIN_PARA ChainPara; CERT_ENHKEY_USAGE EnhKeyUsage, *pEnhKeyUsage; LPSTR rgpszOids[2]; DWORD cElement, cbHashLen, cbUsageLen, i; BOOL bFound = FALSE, bFoundCodeSigning, bFoundWindowsVerification; BYTE rgbHashBuffer[SHA1_HASH_LEN]; BYTE rgbUsage[1024]; ULARGE_INTEGER CertTime, PrevCertTime; PrevCertTime.QuadPart = 0; __try { for (pPrevCertContext = NULL; ;pPrevCertContext = pCertContext) { pCertContext = (PCERT_CONTEXT) CertFindCertificateInStore( hCertStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_ISSUER_STR, pwszCAName, pPrevCertContext ); if (pCertContext == NULL) { __leave; } cbUsageLen = sizeof(rgbUsage); pEnhKeyUsage = (PCERT_ENHKEY_USAGE) rgbUsage; if (CertGetEnhancedKeyUsage( pCertContext, 0, pEnhKeyUsage, &cbUsageLen ) == FALSE) { PrintMessage(MSG_ERROR_S, (L"Can't get certificate usage\n")); __leave; } bFoundCodeSigning = FALSE; bFoundWindowsVerification = FALSE; for (i = 0; i < pEnhKeyUsage->cUsageIdentifier; i++) { if (strcmp( pEnhKeyUsage->rgpszUsageIdentifier[i], szOID_PKIX_KP_CODE_SIGNING) == 0) { bFoundCodeSigning = TRUE; } if (strcmp( pEnhKeyUsage->rgpszUsageIdentifier[i], szOID_WINDOWS_SYSTEM_COMPONENT_VERIFICATION) == 0) { bFoundWindowsVerification = TRUE; } if (bFoundCodeSigning && bFoundWindowsVerification) { break; } } if (bFoundCodeSigning == FALSE || bFoundWindowsVerification == FALSE) { PrintMessage(MSG_WARNING_S, (L"Certificate issued by CA %s in 'MY store' can't be used for Windows build signing\n", pwszCAName)); continue; } bFound = FALSE; memset(rgbHashBuffer, 0, sizeof(rgbHashBuffer)); // the user specified a hash for the root cert // check if this cert chains up to this root. if (memcmp(pbSHA1Hash, rgbHashBuffer, sizeof(rgbHashBuffer)) != 0) { rgpszOids[0] = szOID_PKIX_KP_CODE_SIGNING; rgpszOids[1] = szOID_WINDOWS_SYSTEM_COMPONENT_VERIFICATION; ChainPara.cbSize = sizeof(ChainPara); ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; ChainPara.RequestedUsage.Usage.cUsageIdentifier = 2; ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgpszOids; if (CertGetCertificateChain( NULL, pCertContext, NULL, NULL, &ChainPara, 0, NULL, (CERT_CHAIN_CONTEXT const **) &pChainContext ) == FALSE) { __leave; } // get to the root cert of this chain cElement = pChainContext->rgpChain[0]->cElement; pRootContext = (PCERT_CONTEXT) pChainContext->rgpChain[0]->rgpElement[cElement - 1]->pCertContext; cbHashLen = sizeof(rgbHashBuffer); if (CertGetCertificateContextProperty( pRootContext, CERT_SHA1_HASH_PROP_ID, rgbHashBuffer, &cbHashLen ) == FALSE) { PrintMessage(MSG_ERROR_S, (L"Can't get SHA1 hash for Windows build signing certificate\n")); __leave; } // check if this is the root cert we want if (memcmp(rgbHashBuffer, pbSHA1Hash, cbHashLen) != 0) { PrintMessage(MSG_WARNING_S, (L"Found Windows build signing certificate does not chain up to")); PrintMessage(MSG_WARNING_C, (L"requested root cert (wrong hash provided with -roothash?)\n")); continue; __leave; } bFound = TRUE; break; } // check the cert time of this cert against the time of the prev. valid cert. memcpy(&CertTime, &pCertContext->pCertInfo->NotAfter, sizeof(CertTime)); if (CertTime.QuadPart > PrevCertTime.QuadPart) { if (pReturnCertContext) { CertFreeCertificateContext(pReturnCertContext); } pReturnCertContext = (PCERT_CONTEXT) CertDuplicateCertificateContext(pCertContext); PrevCertTime.QuadPart = CertTime.QuadPart; } } } __finally { if (pChainContext) { CertFreeCertificateChain(pChainContext); } if (bFound == FALSE) { CertFreeCertificateContext(pCertContext); pCertContext = NULL; } } return pReturnCertContext; } HCAINFO CheckCA( LPWSTR pwszCAName ) { LPWSTR *pwszCertTypes = NULL, pwszSanitizeName = NULL; HCAINFO hCAInfo = NULL; BOOL bFoundCodeSigningCA = FALSE; int i; __try { if ((pwszSanitizeName = mySanitizeName(pwszCAName)) == NULL) { __leave; } // scan through the list of CAs to find a valid CA name if (CAFindByName( pwszSanitizeName, NULL, 0, &hCAInfo ) != S_OK) { PrintMessage(MSG_WARNING_S, (L"Can't find CA %s\n", pwszCAName)); __leave; } // get the list of certificate templates that this CA can issue if (CAGetCAProperty( hCAInfo, CA_PROP_CERT_TYPES, &pwszCertTypes ) != S_OK) { PrintMessage(MSG_ERROR_S, (L"Unable to retrieve certificate template list from CA %s\n", pwszCAName)); __leave; } for (i = 0; pwszCertTypes[i]; i++) { if (wcscmp (pwszCertTypes[i], wszCERTTYPE_CODE_SIGNING) == 0) { bFoundCodeSigningCA = TRUE; break; } } } __finally { if (pwszCertTypes) { CAFreeCAProperty(hCAInfo, pwszCertTypes); } if (bFoundCodeSigningCA == FALSE && hCAInfo) { CACloseCA(hCAInfo); hCAInfo = NULL; } if (pwszSanitizeName != NULL) { LocalFree(pwszSanitizeName); } } return hCAInfo; } void PrintHelp( void ) { printf("Usage: csenroll -ca CAName [-ca CAName] [-d Days] [-h] [-roothash Hash]\n"); printf(" Enroll for a Windows build signing certificate\n"); printf("\n"); printf(" Options:\n"); printf(" -ca Name of CA from where to get a certificate\n"); printf(" For backup purposes you can specify multiple names.\n"); printf("\n"); printf(" -d Number of days before expiration of current certificate\n"); printf(" when an attempt is made to renew current certificate.\n"); printf("\n"); printf(" -h Show this help.\n"); printf("\n"); printf(" -roothash Specifiy the hash of the root certificate that the current\n"); printf(" Windows build signing certficate has to chain up to.\n"); printf(" Use this option to ensure that your Windows build signing\n"); printf(" certificate chains up to the correct root certificate\n"); printf("\n"); printf("\n"); } int __cdecl main(int argc, char **argv) { PCERT_CONTEXT pCertContext = NULL; HCAINFO hCAInfo = NULL; HCERTSTORE hCertStore = NULL; LPWSTR pwszCAName = NULL, *pwszCAList = NULL, *pwszDNSName = NULL; BYTE rgbRootHash[SHA1_HASH_LEN]; BOOL bRootHash = FALSE, bRet = FALSE; int i, dwNumCA = 0, dwDaysValid = 0, dwDays = 10; memset(rgbRootHash, 0, sizeof(rgbRootHash)); __try { while (--argc) { argv += 1; if (_stricmp(*argv, "-ca") == 0) { int dwLen; argc -= 1; argv += 1; dwLen = (strlen(*argv) + 1) * sizeof(WCHAR); if (pwszCAList == NULL) { pwszCAList = (LPWSTR *) malloc(sizeof(LPWSTR) * (dwNumCA + 2)); } else { pwszCAList = (LPWSTR *) realloc(pwszCAList, sizeof(LPWSTR) * (dwNumCA + 2)); } if (pwszCAList == NULL) { PrintMessage(MSG_ERROR_S, (L"Failed to allocate memory\n")); __leave; } pwszCAList[dwNumCA + 1] = NULL; pwszCAList[dwNumCA] = (LPWSTR) malloc(dwLen); if (pwszCAList[dwNumCA] == NULL) { PrintMessage(MSG_ERROR_S, (L"Failed to allocate memory\n")); __leave; } MultiByteToWideChar( CP_UTF8, 0, *argv, -1, pwszCAList[dwNumCA], dwLen ); dwNumCA += 1; } if (_stricmp(*argv, "-roothash") == 0) { argv += 1; argc -= 1; if (strlen(*argv) != SHA1_HASH_LEN * 2) { PrintMessage(MSG_ERROR_S, (L"Hash must have a length of 40 bytes\n")); __leave; } _strupr(*argv); for (i = 0; i < SHA1_HASH_LEN * 2; i++) { BYTE bNum; if ((*argv)[i] >= '0' && (*argv)[i] <= '9') { bNum = (*argv)[i] - '0'; } else if ((*argv)[i] >= 'A' && (*argv)[i] <= 'F') { bNum = (*argv)[i] - 'A' + 10; } else { PrintMessage(MSG_ERROR_S, (L"Illegal hexdecimal number in hash\n")); __leave; } rgbRootHash[i / 2] |= (i % 2 ? bNum : bNum << 4); } bRootHash = TRUE; } if (_stricmp(*argv, "-v") == 0) { dwMsgLevel |= MSG_WARNING_S | MSG_WARNING_C; } if (_stricmp(*argv, "-d") == 0) { argv += 1; argc -= 1; dwDays = atoi(*argv); } if (_stricmp(*argv, "-h") == 0) { PrintHelp(); __leave; } } if (dwNumCA == 0) { PrintHelp(); __leave; } hCertStore = CertOpenSystemStore( 0, L"MY" ); if (hCertStore == NULL) { PrintMessage(MSG_ERROR_S, (L"Can't open 'MY store'\n")); __leave; } if (bRootHash == FALSE) { PrintMessage(MSG_WARNING_S, (L"Certificate chain can't be verified (no -roothash specified)\n")); } for (i = 0; i < dwNumCA; i++) { // // now try to find a Windows build signing cert that was issued // from a known ca and that chains up to a known root // pCertContext = FindCodeSigningCertificate( hCertStore, pwszCAList[i], rgbRootHash ); if (pCertContext) { pwszCAName = pwszCAList[i]; break; } } if (pCertContext) { // check how long the current cert is valid ULARGE_INTEGER CertTime, CurrentTime, days; FILETIME SystemTime, LocalTime; memcpy(&CertTime, &pCertContext->pCertInfo->NotAfter, sizeof(CertTime)); GetSystemTimeAsFileTime(&SystemTime); memcpy(&CurrentTime, &SystemTime, sizeof(CurrentTime)); if (CertTime.QuadPart > CurrentTime.QuadPart) { dwDaysValid = (DWORD) ((CertTime.QuadPart - CurrentTime.QuadPart) / (10000000i64 * 24 * 60 * 60)); } // // now check if the CA that originally issued // the cert is still available // if ((hCAInfo = CheckCA(pwszCAName)) == NULL) { PrintMessage(MSG_WARNING_S, (L"Can't find CA %s to renew certificate\n", pwszCAName)); if (dwDaysValid < (dwDays / 2)) { // since we won't be able to renew this cert, just get a new one. CertFreeCertificateContext(pCertContext); pCertContext = NULL; } } } if (pCertContext == NULL) { // find a CA that can issue a windows build signing cert for (i = 0; i < dwNumCA; i++) { if (hCAInfo = CheckCA(pwszCAList[i])) { pwszCAName = pwszCAList[i]; break; } } } if (pCertContext == NULL && hCAInfo == NULL) { PrintMessage(MSG_ERROR_S, (L"Can't find Windows build signing CA\n")); __leave; } if (hCAInfo && (pCertContext == NULL || dwDaysValid < dwDays)) { PrintMessage( MSG_INFO_S, (L"%s build signing certificate. Please wait...\n", pCertContext ? L"Renewing" : L"Enrolling for") ); // get DNS name of CA if (CAGetCAProperty( hCAInfo, CA_PROP_DNSNAME, &pwszDNSName ) != S_OK) { PrintMessage(MSG_ERROR_S, (L"Unable to retrieve DNS name for %s\n", pwszCAName)); __leave; } if (EnrollForCodeSigningCertificate( pwszCAName, *pwszDNSName, pCertContext ) == FALSE) { PrintMessage(MSG_ERROR_S, (L"Enrollment for Windows build signing certificate failed\n")); PrintMessage(MSG_ERROR_C, (L"Check access rights to CA %s and\n", pwszCAName)); PrintMessage(MSG_ERROR_C, (L"Windows build signing certificate template\n")); __leave; } // now make sure that we really have a cert in the store if (pCertContext) { CertFreeCertificateContext(pCertContext); pCertContext = NULL; } if (CertControlStore( hCertStore, 0, CERT_STORE_CTRL_RESYNC, NULL ) == FALSE) { PrintMessage(MSG_ERROR_S, (L"Failed to resync the 'MY store'\n")); __leave; } pCertContext = FindCodeSigningCertificate( hCertStore, pwszCAName, rgbRootHash ); if (pCertContext == NULL) { PrintMessage(MSG_ERROR_S, (L"Can't find valid Windows build signing certificate in 'MY store'\n")); __leave; } } if (ExportCertificateHash(pCertContext) == FALSE) { PrintMessage(MSG_ERROR_S, (L"Export of Windows build signing certificate has failed\n")); __leave; } bRet = TRUE; } __finally { if (pwszDNSName) { CAFreeCAProperty(hCAInfo, pwszDNSName); } if (hCAInfo) { CACloseCA(hCAInfo); } if (pCertContext) { CertFreeCertificateContext(pCertContext); } if (hCertStore) { CertCloseStore(hCertStore, 0); } } return (bRet ? 0 : -1); }