如何以编程方式为 Internet Information Server (IIS) 安装 SSL 证书

文章翻译 文章翻译
文章编号: 313624 - 查看本文应用于的产品
展开全部 | 关闭全部

本文内容

重要说明:本文包含有关编辑元数据库的信息。编辑元数据库之前,请务必保留一个备份副本,如果发生问题,可以还原该副本。有关如何执行此操作的信息,请参见 Microsoft 管理控制台 (MMC) 中的“配置备份/还原”帮助主题。

概要


本文分步介绍了如何以编程方式为 Internet Information Server (IIS) 创建并安装安全套接字层 (SSL) 证书。虽然 IIS 版本 4.0 和 IIS 版本 5.0 各自提供了一个用于创建并安装 SSL 服务器证书的用户界面 (UI),但是您也可以通过编程方式实现该目的。



创建并安装 SSL 证书的步骤

要以编程方式为 IIS 服务器创建并安装 SSL 证书,请按照下列步骤操作:
  1. 您必须将请求发送到证书颁发机构,然后才能发布服务器证书。如果您已经持有证书,并已经将其存储在 IIS 服务器上的文件中,则不要发送请求。
  2. 将证书导入到合适的证书存储区。
  3. 配置 IIS 以使用在步骤 1 中获取的证书。



配置 IIS 以创建并安装 SSL 证书

警告:如果编辑元数据库不当,可能会导致严重问题,您可能需要重新安装使用元数据库的所有产品。Microsoft 不能保证由于您错误地编辑元数据库而导致的问题能够得到解决。编辑元数据库需要您自担风险。

注意:每次编辑元数据库前都要进行备份。 要配置 IIS 以创建并安装 SSL 证书,请按照下列步骤操作:
  1. 使用 Microsoft Windows Crypto API 获取证书的指纹属性。
  2. 必须将 IIS 元数据库属性 SSLCertHash 设置为指纹的值。
  3. 必须将 IIS 元数据库属性 SSLStoreName 设置为要使用的存储区。
请参见下文中的代码示例,以获取证书的指纹,然后再获取 SSLCertHash 属性:
用于获取服务器身份验证证书指纹的 C 代码
用来将 SSLCertHash 属性输入到元数据库中的 C 代码



在特定网站上启用 SSL 的步骤

执行“配置 IIS 以创建并安装 SSL 证书”一节中的步骤后,可以在特定网站或特定文件夹中启用 SSL。为此,您必须在要使用的文件夹中启用 SSL 选项。以下步骤与在“配置 IIS 以创建并安装 SSL 证书”一节中描述的各步骤相对应:
  1. 调用 CEnroll::createPKCS10() 方法时,会创建证书请求。将用法设置为以下预定义值:szOID_PKIX_KP_SERVER_AUTH
    #define szOID_PKIX_KP_SERVER_AUTH       "1.3.6.1.5.5.7.3.1"		
  2. 调用 ICertRequest::Submit () 方法时,会将一个证书请求提交给证书颁发机构。
  3. 证书可以从证书颁发机构检索,然后安装在合适的存储区中。IIS 服务器证书向导只查找本地计算机证书存储区中可用于服务器身份验证的证书。




配置 IIS 的步骤

将证书保存到存储区后,必须按照以下方法配置 IIS:
  1. 使用 CertGetCertificateContextProperty() 方法获取指纹属性和 CERT_HASH_PROP_ID 属性的值。CertGetCertificateContextProperty() 函数使用 CryptHashCertificate() 方法来计算 CERT_HASH_PROP_ID 属性的值。如果 HASH 值不存在,则 CertGetCertificateContextProperty() 会返回 SHA1 算法。
  2. 您必须创建新的二进制元数据库属性 SSLCertHash,它与网站相对应。然后将 SSLCertHash 设置为在本节步骤 1 中获取的证书指纹。如果发生此问题,架构会将 SSLCertHash 属性错误地指定为展开的以空字符结尾的字符串,而不是二进制数据,因此无法使用 IIS 管理对象来导入 SSLCertHash。只能使用 IIS 管理基本对象来导入此值。要使用 IIS 管理基本对象导入 SSLCertHash,您必须使用十进制值 5506。
  3. 必须为对应的网站创建新的字符串元数据库属性 SSLStoreName。将 SSLStoreName 设置为字符串值 MY。您可以通过 IIS 管理对象(例如,采用 ADSI 脚本)或 IIS 管理基本对象设置 SSLStoreName。要使用 IIS 管理基本对象来设置 SSLStoreName,请使用十进制值 5511。




用来获取服务器身份验证证书指纹的 C 代码

以下 Microsoft C 代码示例描述了如何获取服务器身份验证证书的指纹属性:
#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>
#define MY_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

//--------------------------------------------------------------------
//    Define the name of the store where the needed certificate
//    can be found. 

#define CERT_STORE_NAME  L"MY"

//--------------------------------------------------------------------
//   Declare local functions.
//   Local function definitions follow main.

void HandleError(char *s);

void main(void)
{
//--------------------------------------------------------------------
// Declare and initialize local variables. 
// This includes initializing a pointer to the message. 
// Usually, the message will exist somewhere and a pointer will
// be passed to the application.

//--------------------------------------------------------------------
// System store handle

HCERTSTORE hStoreHandle;   

//--------------------------------------------------------------------
// Pointer to a certificate

PCCERT_CONTEXT pCert;
PCCERT_CONTEXT pPrevCert;
 
LPBYTE pEncodedBytes = NULL;
LPBYTE pHash;
DWORD cbData, i;

//--------------------------------------------------------------------
// Open a certificate store.

if ( !( hStoreHandle = CertOpenStore(
   CERT_STORE_PROV_SYSTEM,
   0,
   NULL,
   CERT_SYSTEM_STORE_LOCAL_MACHINE,
   CERT_STORE_NAME)))
{
     HandleError("The MY store could not be opened.");
}


pPrevCert = NULL;

for (; ((pCert = CertEnumCertificatesInStore(hStoreHandle, pPrevCert)) 
	                                         != NULL);
    pPrevCert = pCert)
{
    CERT_ENHKEY_USAGE *pKeyUsage;
    DWORD j, nLen;
    BOOL bFound = FALSE;
    char certName[1024];

    nLen = sizeof(certName);
    certName[0] = 0;
    if (CertNameToStr(MY_TYPE, &(pCert->pCertInfo->Subject),
        CERT_X500_NAME_STR,
        certName,
        sizeof(certName)))
    {
        printf("Checking %s certificate\n", certName);
    }

    cbData = 0;
    if (!CertGetEnhancedKeyUsage(pCert,
        0,
        NULL,
        &cbData) || cbData == 0)
    {
        if (GetLastError() == CRYPT_E_NOT_FOUND)
        {
            printf("%s certificate is for all key usages\n", certName);
            break;
        }
        else
            printf("CertGetEnhancedKeyUsage failed with error code : %08X\n",
                GetLastError());
    }
    pKeyUsage = (CERT_ENHKEY_USAGE *)
        HeapAlloc(GetProcessHeap(), 0, cbData);
    if (pKeyUsage == NULL)
    {
        printf("HeapAlloc failed with error code : %08X\n",
            GetLastError());
        HandleError("Certificate not found.");
    }
    if (!CertGetEnhancedKeyUsage(pCert,
        0,
        pKeyUsage,
        &cbData))
    {
        if (GetLastError() == CRYPT_E_NOT_FOUND)
        {
            printf("%s certificate is for all key usages\n", certName);
            HeapFree(GetProcessHeap(), 0, pKeyUsage);
            break;
        }
        else
        {
            printf("CertGetEnhancedKeyUsage failed with error code : %08X\n",
                GetLastError());
            HeapFree(GetProcessHeap(), 0, pKeyUsage);
            continue;
        }
    }

    if (pKeyUsage->cUsageIdentifier == 0)
    {
        printf("%s certificate is for all key usages\n", certName);
        HeapFree(GetProcessHeap(), 0, pKeyUsage);
        break;
    }

    bFound = FALSE;
    for (j = 0; j < pKeyUsage->cUsageIdentifier; j++)
    {
        if (strcmpi(pKeyUsage->rgpszUsageIdentifier[j], 
			        szOID_PKIX_KP_SERVER_AUTH) == 0)
        {
            printf("%s certificate is for Server Authentication\n", 
				   certName);
            bFound = TRUE;
            break;
        }
    }

    HeapFree(GetProcessHeap(), 0, pKeyUsage);
    if (bFound)
        break;
}

if (pCert == NULL)
   HandleError("Certificate not found.");

if (pPrevCert)
{
    CertFreeCertificateContext(pPrevCert);
    pPrevCert = NULL;
}

/// CASE 2 Get the hash from the certificate
pHash = NULL;
cbData = 0;
CertGetCertificateContextProperty(pCert, CERT_HASH_PROP_ID, NULL, &cbData);
if (cbData == 0)
{
   HandleError("CertGetCertificateContextProperty 1 failed");
}

pHash = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, cbData);
if (pHash == NULL)
{
   HandleError("HeapAlloc failed");
}
if (!CertGetCertificateContextProperty(pCert, CERT_HASH_PROP_ID, pHash, 
	                                   &cbData))
{
   HandleError("CertGetCertificateContextProperty 2 failed");
}

printf("CERT_HASH_PROP_ID Length is %d\n", cbData);
printf("CERT_HASH_PROP_ID BYTES [", cbData);

for (i = 0; i < cbData; i++)
{
    printf("%02X", pHash[i]);
}
printf("]\n");

//--------------------------------------------------------------------
// Clean up and free memory.

if (pEncodedBytes)
    HeapFree(GetProcessHeap(), 0, pEncodedBytes);

if (pHash)
    HeapFree(GetProcessHeap(), 0, pHash);

if(pCert)
     CertFreeCertificateContext(pCert);
if(CertCloseStore(
      hStoreHandle, 
      CERT_CLOSE_STORE_CHECK_FLAG))
{
    printf("The store closed and all certificates are freed. \n");
}
else
{
    printf("Store closed -- \n"
          "not all certificates, CRLs or CTLs were freed");
}
} // End of main

//--------------------------------------------------------------------
//  This example uses the function HandleError, a simple error
//  handling function, to print an error message to the standard error 
//  (stderr) file and exit the program. 
//  For most applications, replace this function with one 
//  that does more extensive error reporting.

void HandleError(char *s)
{
    fprintf(stderr,"An error occurred in running the program. \n");
    fprintf(stderr,"%s\n",s);
    fprintf(stderr, "Error number %x.\n", GetLastError());
    fprintf(stderr, "Program terminating. \n");
    exit(1);
} // End of HandleError




用来将 SSLCertHash 属性输入到元数据库中的 C 代码

以下示例代码介绍了如何使用 IIS 管理基本对象来设置元数据库中的 SSLCertHash 方法。下面的代码使用任意二进制数组作为证书指纹。实际代码将使用在“获取服务器身份验证证书的指纹”一节中运行代码时获得的指纹值。您可以设置 SSLCertHash;如果您已经配置了网站的 SSL,也可以获取已有的 SSLCertHash。具体采用哪种方式取决于您在编译时如何定义 SetData 方法。SSLCertHash 方法会返回现有的 CertHash
#define UNICODE // unicode must be defined for Metabase access
#define INITGUID 
#include <windows.h>
#include <httpfilt.h>
#include <stdio.h> 

#define SETDATA

#include <iadmw.h>    // COM Interface header 
#include <iiscnfg.h>  // MD_ & IIS_MD_ #defines 



extern "C" wmain (int argc, TCHAR ** argv)
{  
	IMSAdminBase  *pIMeta;  
	METADATA_HANDLE MyHandle; 
    HRESULT hres;
   	METADATA_RECORD record = {0};
    TCHAR szError [2048];
	BYTE *myData=NULL;
    DWORD dwSize = sizeof (record);
 	DWORD i;

	// this just a sample of some thumbprint
	BYTE bar[]={0x24, 0xC6, 0xBA, 0xBB, 0x81, 0x76, 0x05, 0xC9, 0xC3, 
		        0x97, 0x6D, 0x4D, 0xEB, 0x85, 0x8F, 0x4F, 0xBF, 0x38,
				0xFD};
    
    
    CoInitialize (NULL);
    
    // get a pointer to the IIS Admin Base Object
	hres = CoCreateInstance(CLSID_MSAdminBase, NULL, CLSCTX_ALL, 
			IID_IMSAdminBase, (void **) &pIMeta);  
	if (FAILED(hres))  
	{
	    wsprintf (szError, L"CoCreateInstance Failed. Error: %x\n", hres);
        printf ("%S\n", szError);
		CoUninitialize();
        return TRUE;  
	}
    
    // for this test use only 1st server instance
    hres = pIMeta->OpenKey(METADATA_MASTER_ROOT_HANDLE, L"/LM", 
		METADATA_PERMISSION_READ|METADATA_PERMISSION_WRITE, 20, &MyHandle); 
	if (FAILED (hres) )
	{
		wsprintf (szError, L"OpenKey Failed. Error: %x\n", hres);
        printf ("%S\n", szError);
		goto clean;
	}
    
	// SSLCertHash = 5506
	record.dwMDIdentifier =  5506;
    record.dwMDAttributes =METADATA_INHERIT;
    record.dwMDUserType=IIS_MD_UT_SERVER;
    record.dwMDDataType= BINARY_METADATA;
    record.pbMDData = (unsigned char *) myData;

#ifndef SETDATA
#pragma message ("Building for GetData\n")    
	again:
    hres = pIMeta->GetData (MyHandle,argv[1], &record, &dwSize);
   	if (FAILED (hres) )
	{
		if (hres == MD_ERROR_DATA_NOT_FOUND)
		{
			printf ("%S\n", L"Data not found, no certificate is set!");
			goto clean;
		}
		else if (HRESULT_CODE(hres)==ERROR_INSUFFICIENT_BUFFER)
		{
			record.dwMDDataLen=dwSize;
			myData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, dwSize);
			record.pbMDData  = (unsigned char *)myData;
		    goto again;
		}
		else  
		{
			wsprintf (szError, L"GetData Failed. Error: %x\n", hres);
			printf ("%S\n", szError);
			goto clean;;
		}
	}

	printf ("%S", L"Got thumbprint. You can compare" 
		          L" it with the MMC for IIS value:\n");	
	for ( i=0; i<(record.dwMDDataLen/sizeof (BYTE)); i++)		
		printf ("%2X ", myData[i]);  
	HeapFree(GetProcessHeap(), 0, myData);
#else
#pragma message ("Building for SetData\n")

	
	record.pbMDData = bar;
	record.dwMDDataLen = 19; // in real code it should be the size 
                                                   // of the thumbprint buffer
	hres = pIMeta->SetData (MyHandle,argv[1], &record);
	if (FAILED (hres) )
	{
		printf ("Set data failed: 0x%x!\n", hres);
		goto clean;
	}
	else
		printf ("New thumbprint is set\n");
#endif

clean:
	pIMeta->CloseKey(MyHandle); 
	pIMeta->SaveData();
	pIMeta->Release();
	CoUninitialize();

	return 1;
}



参考

有关更多信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章:
315588 如何使用客户端证书保护 ASP.NET 应用程序的安全

属性

文章编号: 313624 - 最后修改: 2006年8月4日 - 修订: 2.1
这篇文章中的信息适用于:
  • Microsoft Internet Information Server 4.0
  • Microsoft Internet Information Services 5.0
关键字:?
kbcrypt kbapi kbhowtomaster kbsecurity kbisapiext kbhowto KB313624
Microsoft和/或其各供应商对于为任何目的而在本服务器上发布的文件及有关图形所含信息的适用性,不作任何声明。 所有该等文件及有关图形均"依样"提供,而不带任何性质的保证。Microsoft和/或其各供应商特此声明,对所有与该等信息有关的保证和条件不负任何责任,该等保证和条件包括关于适销性、符合特定用途、所有权和非侵权的所有默示保证和条件。在任何情况下,在由于使用或运行本服务器上的信息所引起的或与该等使用或运行有关的诉讼中,Microsoft和/或其各供应商就因丧失使用、数据或利润所导致的任何特别的、间接的、衍生性的损害或任何因使用而丧失所导致的之损害、数据或利润不负任何责任。

提供反馈

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com