How to programmatically install SSL certificates for Internet Information Server (IIS) (313624)



The information in this article applies to:

  • Microsoft Internet Information Server 4.0
  • Microsoft Internet Information Server 5.0

This article was previously published under Q313624
Important This article contains information about editing the metabase. Before you edit the metabase, verify that you have a backup copy that you can restore if a problem occurs. For information about how to do this, see the "Configuration Backup/Restore" Help topic in Microsoft Management Console (MMC).

SUMMARY


This step-by-step article describes how to programmatically create and then to install Secure Sockets Layer (SSL) certificates for Internet Information Server (IIS). While IIS version 4.0 and IIS version 5.0 each provide a user interface (UI) for you to create and then to install SSL Server certificates, you can also complete the task programmatically.

back to the top

Steps to Create and Then to Install SSL Certificates

To programmatically create and then to install SSL certificates for IIS server, follow these steps:
  1. You must send a request to the certification authority to issue a server certificate. If you already have the certificate, and then you store it in the file on the IIS server, do not send the request.
  2. Import the certificate to the appropriate certificate store.
  3. Configure IIS to use the certificate that you obtained in step 1.

back to the top

Configure IIS to Create and Then to Install SSL Certificates

Warning If you edit the metabase incorrectly, you can cause serious problems that may require you to reinstall any product that uses the metabase. Microsoft cannot guarantee that problems that result if you incorrectly edit the metabase can be solved. Edit the metabase at your own risk.

Note Always back up the metabase before you edit it. Follow these steps to configure IIS to create and then to install SSL certificates:
  1. Use Microsoft Windows Crypto APIs to obtain the Thumbprint property of the certificate.
  2. You must set the IIS metabase property, SSLCertHash, to the value of the Thumbprint.
  3. You must set the IIS metabase property, SSLStoreName, to the store that you want to use.
See the following article sections for code samples to obtain a Thumbprint of the certificate, and then to obtain the SSLCertHash property:
C Code to Obtain the Thumbprint of a Server Authentication Certificate
C Code to Enter the SSLCertHash Property into the Metabase

back to the top

Steps to Enable SSL on a Particular Web Site

After you perform the steps in the "Configure IIS to Create and Then to Install SSL Certificates" section, then you can enable SSL on a particular site or in a particular folder. To do so, you must enable SSL options in the folder that you want to use. The following steps correspond with each step that is outlined in the "Configure IIS to Create and Then to Install SSL Certificates" section:
  1. When you call the CEnroll::createPKCS10() method, the certificate request is created. Set the usage to the following predefined value: szOID_PKIX_KP_SERVER_AUTH:
    #define szOID_PKIX_KP_SERVER_AUTH       "1.3.6.1.5.5.7.3.1"		
  2. When you call the ICertRequest::Submit () method, a request for the certificate is submitted to the certification authority.
  3. The certificate is retrieved from the certification authority, and then it is installed in the appropriate store. The IIS Server Certificate Wizard looks only for certificates that it can use for server authentication in the Local Computer certificate store.


back to the top

Steps to Configure IIS

After you save the certificate to the store, you must configure IIS as follows:
  1. Use the CertGetCertificateContextProperty()method to obtain the values of the Thumbprint property and the CERT_HASH_PROP_ID property. The CertGetCertificateContextProperty() function uses the CryptHashCertificate() method to compute the value for the CERT_HASH_PROP_ID property. If the HASH value does not exist, CertGetCertificateContextProperty() returns the SHA1 algorithm.
  2. You must create a new binary metabase property, SSLCertHash, that corresponds to the Web site. Set SSLCertHash to the certificate Thumbprint that you obtained in step 1 of this section. When this problem occurs, the schema incorrectly specifies the SSLCertHash property as an expanded null terminated string, instead of as binary data, and then you cannot use IIS Admin Objects to import SSLCertHash. You can use only IIS Admin Base objects to import this value. To use SSLCertHash with IIS Admin Base objects, you must use the Decimal value, 5506.
  3. You must create a new string metabase property, SSLStoreName, for the corresponding Web site. Set SSLStoreName to the string MY value. You can either set SSLStoreName through IIS Admin Objects (for example, in ADSI Script) or through IIS Admin Base objects. To use SSLStoreName with IIS Admin base objects, use the Decimal value, 5511.


back to the top

C Code to Obtain the Thumbprint of a Server Authentication Certificate

The following sample Microsoft C code describes how to obtain the Thumbprint property of a Server Authentication certificate:
#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


back to the top

C Code to Enter the SSLCertHash Property into the Metabase

The following sample code describes how to use IIS Admin Base objects to set the SSLCertHash method in the metabase. The following code uses an arbitrary binary array as a certificate Thumbprint. Actual code would use the Thumbprint value that you obtain when you run the code in the "Obtain the Thumbprint of a Server Authentication Certificate" section. Depending on how you define the SetData method at compile time, you can either set SSLCertHash, or, if you already configured SSL for the Web site, you can obtain the SSLCertHash that exists. The SSLCertHash method return the existing 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;
}


back to the top

REFERENCES

For more information, click the following article number to view the article in the Microsoft Knowledge Base:

315588 How to secure an ASP.NET application using client-side certificates


back to the top

Modification Type:MajorLast Reviewed:7/11/2006
Keywords:kbCrypt kbAPI kbHOWTOmaster kbSecurity kbISAPIExt kbhowto KB313624 kbAudDeveloper