我们正在开发许多WCF服务.请求将越过域边界; 也就是说,客户端在一个域中运行,处理请求的服务器位于不同的(生产)域中.我知道如何使用SSL和证书保护此链接.我们将询问用户在生产域上的用户名和密码,并将其传递到SOAP头中.
我的问题是在开发和"beta"测试期间该做什么.我知道我可以获得临时证书并在开发期间使用它.我想知道我对这种方法的替代方案是什么.其他人在这种情况下做了什么?
更新:我不确定我的问题得到了"好"的回答.我是一个大型团队(50+)开发人员的一员.该组织相当敏捷.任何一个开发人员都可能最终处理使用WCF的项目.事实上,其他几个项目正在做类似的事情,但针对不同的网站和服务.我正在寻找的是一种方式,我可以让任何人进入这个特定的项目几天,而不必跳过一些箍.安装开发证书就是其中之一.我完全理解在开发过程中对WCF结构进行"dogfooding"是最佳实践.大多数答案都给出了答案.我想知道什么是有意义的,而不是"
乔恩
更新:我们实际上使用更简单的Keith Brown解决方案而不是现在,请参阅他提供的源代码.优点:无需维护非托管代码.
如果您仍想查看如何使用C/C++进行操作,请继续阅读...
仅推荐用于开发当然,而不是生产,但有一种低摩擦方式来生成X.509证书(不求助于makecert.exe
).
如果您可以在Windows上访问CryptoAPI,那么您的想法是使用CryptoAPI调用生成RSA公钥和私钥,对新的X.509证书进行签名和编码,将其放入仅内存的证书库中,然后使用PFXExportCertStore()
生成.pfx字节,然后您可以传递给X509Certificate2
构造函数.
一旦有了X509Certificate2
实例,就可以将它设置为相应WCF对象的属性,然后开始工作.
我有一些我编写的示例代码,不保证任何类型的过程,并且你需要一些C经验来编写必须不受管理的位(编写P /会更痛苦)调用所有CryptoAPI调用,而不是让该部分在C/C++中).
示例C#代码使用非托管辅助函数:
public X509Certificate2 GenerateSelfSignedCertificate(string issuerCommonName, string keyPassword) { int pfxSize = -1; IntPtr pfxBufferPtr = IntPtr.Zero; IntPtr errorMessagePtr = IntPtr.Zero; try { if (!X509GenerateSelfSignedCertificate(KeyContainerName, issuerCommonName, keyPassword, ref pfxSize, ref pfxBufferPtr, ref errorMessagePtr)) { string errorMessage = null; if (errorMessagePtr != IntPtr.Zero) { errorMessage = Marshal.PtrToStringUni(errorMessagePtr); } throw new ApplicationException(string.Format("Failed to generate X.509 server certificate. {0}", errorMessage ?? "Unspecified error.")); } if (pfxBufferPtr == IntPtr.Zero) { throw new ApplicationException("Failed to generate X.509 server certificate. PFX buffer not initialized."); } if (pfxSize <= 0) { throw new ApplicationException("Failed to generate X.509 server certificate. PFX buffer size invalid."); } byte[] pfxBuffer = new byte[pfxSize]; Marshal.Copy(pfxBufferPtr, pfxBuffer, 0, pfxSize); return new X509Certificate2(pfxBuffer, keyPassword); } finally { if (pfxBufferPtr != IntPtr.Zero) { Marshal.FreeHGlobal(pfxBufferPtr); } if (errorMessagePtr != IntPtr.Zero) { Marshal.FreeHGlobal(errorMessagePtr); } } }
该X509GenerateSelfSignedCertificate
功能的实现可以去这样的事情(需要WinCrypt.h
):
BOOL X509GenerateSelfSignedCertificate(LPCTSTR keyContainerName, LPCTSTR issuerCommonName, LPCTSTR keyPassword, DWORD *pfxSize, BYTE **pfxBuffer, LPTSTR *errorMessage) { // Constants #define CERT_DN_ATTR_COUNT 1 #define SIZE_SERIALNUMBER 8 #define EXPIRY_YEARS_FROM_NOW 2 #define MAX_COMMON_NAME 8192 #define MAX_PFX_SIZE 65535 // Declarations HCRYPTPROV hProv = NULL; BOOL result = FALSE; // Sanity if (pfxSize != NULL) { *pfxSize = -1; } if (pfxBuffer != NULL) { *pfxBuffer = NULL; } if (errorMessage != NULL) { *errorMessage = NULL; } if (keyContainerName == NULL || _tcslen(issuerCommonName) <= 0) { SetOutputErrorMessage(errorMessage, _T("Key container name must not be NULL or an empty string.")); return FALSE; } if (issuerCommonName == NULL || _tcslen(issuerCommonName) <= 0) { SetOutputErrorMessage(errorMessage, _T("Issuer common name must not be NULL or an empty string.")); return FALSE; } if (keyPassword == NULL || _tcslen(keyPassword) <= 0) { SetOutputErrorMessage(errorMessage, _T("Key password must not be NULL or an empty string.")); return FALSE; } // Start generating USES_CONVERSION; if (CryptAcquireContext(&hProv, keyContainerName, MS_DEF_RSA_SCHANNEL_PROV, PROV_RSA_SCHANNEL, CRYPT_MACHINE_KEYSET) || CryptAcquireContext(&hProv, keyContainerName, MS_DEF_RSA_SCHANNEL_PROV, PROV_RSA_SCHANNEL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET)) { HCRYPTKEY hKey = NULL; // Generate 1024-bit RSA keypair. if (CryptGenKey(hProv, AT_KEYEXCHANGE, CRYPT_EXPORTABLE | RSA1024BIT_KEY, &hKey)) { DWORD pkSize = 0; PCERT_PUBLIC_KEY_INFO pkInfo = NULL; // Export public key for use by certificate signing. if (CryptExportPublicKeyInfo(hProv, AT_KEYEXCHANGE, X509_ASN_ENCODING, NULL, &pkSize) && (pkInfo = (PCERT_PUBLIC_KEY_INFO)LocalAlloc(0, pkSize)) && CryptExportPublicKeyInfo(hProv, AT_KEYEXCHANGE, X509_ASN_ENCODING, pkInfo, &pkSize)) { CERT_RDN_ATTR certDNAttrs[CERT_DN_ATTR_COUNT]; CERT_RDN certDN[CERT_DN_ATTR_COUNT] = {{1, &certDNAttrs[0]}}; CERT_NAME_INFO certNameInfo = {CERT_DN_ATTR_COUNT, &certDN[0]}; DWORD certNameSize = -1; BYTE *certNameData = NULL; certDNAttrs[0].dwValueType = CERT_RDN_UNICODE_STRING; certDNAttrs[0].pszObjId = szOID_COMMON_NAME; certDNAttrs[0].Value.cbData = (DWORD)(_tcslen(issuerCommonName) * sizeof(WCHAR)); certDNAttrs[0].Value.pbData = (BYTE*)T2W((LPTSTR)issuerCommonName); // Encode issuer name into certificate name blob. if (CryptEncodeObject(X509_ASN_ENCODING, X509_NAME, &certNameInfo, NULL, &certNameSize) && (certNameData = (BYTE*)LocalAlloc(0, certNameSize)) && CryptEncodeObject(X509_ASN_ENCODING, X509_NAME, &certNameInfo, certNameData, &certNameSize)) { CERT_NAME_BLOB issuerName; CERT_INFO certInfo; SYSTEMTIME systemTime; FILETIME notBefore; FILETIME notAfter; BYTE serialNumber[SIZE_SERIALNUMBER]; DWORD certSize = -1; BYTE *certData = NULL; issuerName.cbData = certNameSize; issuerName.pbData = certNameData; // Certificate should be valid for a decent window of time. ZeroMemory(&certInfo, sizeof(certInfo)); GetSystemTime(&systemTime); systemTime.wYear -= 1; SystemTimeToFileTime(&systemTime, ¬Before); systemTime.wYear += EXPIRY_YEARS_FROM_NOW; SystemTimeToFileTime(&systemTime, ¬After); // Generate a throwaway serial number. if (CryptGenRandom(hProv, SIZE_SERIALNUMBER, serialNumber)) { certInfo.dwVersion = CERT_V3; certInfo.SerialNumber.cbData = SIZE_SERIALNUMBER; certInfo.SerialNumber.pbData = serialNumber; certInfo.SignatureAlgorithm.pszObjId = szOID_RSA_MD5RSA; certInfo.Issuer = issuerName; certInfo.NotBefore = notBefore; certInfo.NotAfter = notAfter; certInfo.Subject = issuerName; certInfo.SubjectPublicKeyInfo = *pkInfo; // Now sign and encode it. if (CryptSignAndEncodeCertificate(hProv, AT_KEYEXCHANGE, X509_ASN_ENCODING, X509_CERT_TO_BE_SIGNED, (LPVOID)&certInfo, &(certInfo.SignatureAlgorithm), NULL, NULL, &certSize) && (certData = (BYTE*)LocalAlloc(0, certSize)) && CryptSignAndEncodeCertificate(hProv, AT_KEYEXCHANGE, X509_ASN_ENCODING, X509_CERT_TO_BE_SIGNED, (LPVOID)&certInfo, &(certInfo.SignatureAlgorithm), NULL, certData, &certSize)) { HCERTSTORE hCertStore = NULL; // Open a new temporary store. if ((hCertStore = CertOpenStore(CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, CERT_STORE_CREATE_NEW_FLAG, NULL))) { PCCERT_CONTEXT certContext = NULL; // Add to temporary store so we can use the PFX functions to export a store + private keys in PFX format. if (CertAddEncodedCertificateToStore(hCertStore, X509_ASN_ENCODING, certData, certSize, CERT_STORE_ADD_NEW, &certContext)) { CRYPT_KEY_PROV_INFO keyProviderInfo; // Link keypair to certificate (without this the keypair gets "lost" on export). ZeroMemory(&keyProviderInfo, sizeof(keyProviderInfo)); keyProviderInfo.pwszContainerName = T2W((LPTSTR)keyContainerName); keyProviderInfo.pwszProvName = MS_DEF_RSA_SCHANNEL_PROV_W; /* _W used intentionally. struct hardcodes LPWSTR. */ keyProviderInfo.dwProvType = PROV_RSA_SCHANNEL; keyProviderInfo.dwFlags = CRYPT_MACHINE_KEYSET; keyProviderInfo.dwKeySpec = AT_KEYEXCHANGE; // Finally, export to PFX and provide to caller. if (CertSetCertificateContextProperty(certContext, CERT_KEY_PROV_INFO_PROP_ID, 0, (LPVOID)&keyProviderInfo)) { CRYPT_DATA_BLOB pfxBlob; DWORD pfxExportFlags = EXPORT_PRIVATE_KEYS | REPORT_NO_PRIVATE_KEY | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY; // Calculate size required. ZeroMemory(&pfxBlob, sizeof(pfxBlob)); if (PFXExportCertStore(hCertStore, &pfxBlob, T2CW(keyPassword), pfxExportFlags)) { pfxBlob.pbData = (BYTE *)LocalAlloc(0, pfxBlob.cbData); if (pfxBlob.pbData != NULL) { // Now export. if (PFXExportCertStore(hCertStore, &pfxBlob, T2CW(keyPassword), pfxExportFlags)) { if (pfxSize != NULL) { *pfxSize = pfxBlob.cbData; } if (pfxBuffer != NULL) { // Caller must free this. *pfxBuffer = pfxBlob.pbData; } else { // Caller did not provide target pointer to receive buffer, free ourselves. LocalFree(pfxBlob.pbData); } result = TRUE; } else { SetOutputErrorMessage(errorMessage, _T("Failed to export certificate in PFX format (0x%08x)."), GetLastError()); } } else { SetOutputErrorMessage(errorMessage, _T("Failed to export certificate in PFX format, buffer allocation failure (0x%08x)."), GetLastError()); } } else { SetOutputErrorMessage(errorMessage, _T("Failed to export certificate in PFX format, failed to calculate buffer size (0x%08x)."), GetLastError()); } } else { SetOutputErrorMessage(errorMessage, _T("Failed to set certificate key context property (0x%08x)."), GetLastError()); } } else { SetOutputErrorMessage(errorMessage, _T("Failed to add certificate to temporary certificate store (0x%08x)."), GetLastError()); } CertCloseStore(hCertStore, 0); hCertStore = NULL; } else { SetOutputErrorMessage(errorMessage, _T("Failed to create temporary certificate store (0x%08x)."), GetLastError()); } } else { SetOutputErrorMessage(errorMessage, _T("Failed to sign/encode certificate or out of memory (0x%08x)."), GetLastError()); } if (certData != NULL) { LocalFree(certData); certData = NULL; } } else { SetOutputErrorMessage(errorMessage, _T("Failed to generate certificate serial number (0x%08x)."), GetLastError()); } } else { SetOutputErrorMessage(errorMessage, _T("Failed to encode X.509 certificate name into ASN.1 or out of memory (0x%08x)."), GetLastError()); } if (certNameData != NULL) { LocalFree(certNameData); certNameData = NULL; } } else { SetOutputErrorMessage(errorMessage, _T("Failed to export public key blob or out of memory (0x%08x)."), GetLastError()); } if (pkInfo != NULL) { LocalFree(pkInfo); pkInfo = NULL; } CryptDestroyKey(hKey); hKey = NULL; } else { SetOutputErrorMessage(errorMessage, _T("Failed to generate public/private keypair for certificate (0x%08x)."), GetLastError()); } CryptReleaseContext(hProv, 0); hProv = NULL; } else { SetOutputErrorMessage(errorMessage, _T("Failed to acquire cryptographic context (0x%08x)."), GetLastError()); } return result; } void SetOutputErrorMessage(LPTSTR *errorMessage, LPCTSTR formatString, ...) { #define MAX_ERROR_MESSAGE 1024 va_list va; if (errorMessage != NULL) { size_t sizeInBytes = (MAX_ERROR_MESSAGE * sizeof(TCHAR)) + 1; LPTSTR message = (LPTSTR)LocalAlloc(0, sizeInBytes); va_start(va, formatString); ZeroMemory(message, sizeInBytes); if (_vstprintf_s(message, MAX_ERROR_MESSAGE, formatString, va) == -1) { ZeroMemory(message, sizeInBytes); _tcscpy_s(message, MAX_ERROR_MESSAGE, _T("Failed to build error message")); } *errorMessage = message; va_end(va); } }
我们已经使用它来在启动时生成SSL证书,当您只想测试加密而不验证信任/身份时,这很好,并且生成只需要大约2-3秒.