Building a Certificate Request Utility
To get a better
understanding of how these two COM objects work, you'll build a Certificate Request utility. This utility will perform the following steps:
Collect identification information from the user
Allow the user to specify how the certificate will be used
Build the certificate request and send it to the CA
Allow the user to check the status of the request
After the certificate has been issued, retrieve the certificate and accept it into the certificate store
Tip
Because of the nature of this example, trying this utility against a local copy of Microsoft's Certificate Server would be a good idea. This way, you can issue and deny certificate requests that are generated by this utility.
Creating the Certificate Request Class
You start your
project by creating a new, standard executable Visual Basic project. After you've created the application shell and default form, you can turn your attention to the Certificate Request functionality. First, though, you need to include a reference to the CertCli Type Library in the Project, References dialog, as shown in Figure 5.3.
Next, instead of extending the crypto class that you have been building over the last few chapters, you'll create a new class with just certificate request functionality in it. To get started, create a new class, name it clsCertRequest, and add the constant and type declarations in Listing 5.1.
Code Listing 5.1. The clsCertRequest Class Declarations
Option Explicit
'--- Certificate Request Format
Const CR_IN_BASE64HEADER = 0
Const CR_IN_BASE64 = &H1
Const CR_IN_BINARY = &H2
Const CR_IN_ENCODEMASK = &HFF
Const CR_IN_PKCS10 = &H100
Const CR_IN_KEYGEN = &H200
Const CR_IN_FORMATMASK = &HFF00
Const CR_IN_ENCRYPTED_REQUEST = &H10000
Const CR_IN_ENCRYPTED_ATTRIBUTES = &H20000
'--- Certificate Request Status
Const CR_DISP_INCOMPLETE = 0
Const CR_DISP_ERROR = &H1
Const CR_DISP_DENIED = &H2
Const CR_DISP_ISSUED = &H3
Const CR_DISP_ISSUED_OUT_OF_BAND = &H4
Const CR_DISP_UNDER_SUBMISSION = &H5
'--- Issued Certificate Format
Const CR_OUT_BASE64HEADER = 0
Const CR_OUT_BASE64 = &H1
Const CR_OUT_BINARY = &H2
Const CR_OUT_ENCODEMASK = &HFF
Const CR_OUT_CHAIN = &H100
'--- Key Types
Const AT_KEYEXCHANGE = 1
Const AT_SIGNATURE = 2
Public Enum enmKeyType
KT_KeyExchange = AT_KEYEXCHANGE
KT_Signature = AT_SIGNATURE
End Enum
'-------------------------------------------------------------------
' Enhanced Key Usage (Purpose) Object Identifiers
'-------------------------------------------------------------------
Const szOID_PKIX_KP = "1.3.6.1.5.5.7.3"
'--- Consistent key usage bits: DIGITAL_SIGNATURE, KEY_ENCIPHERMENT
'--- or KEY_AGREEMENT
Const szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"
'--- Consistent key usage bits: DIGITAL_SIGNATURE
Const szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2"
'--- Consistent key usage bits: DIGITAL_SIGNATURE
Const szOID_PKIX_KP_CODE_SIGNING = "1.3.6.1.5.5.7.3.3"
'--- Consistent key usage bits: DIGITAL_SIGNATURE, NON_REPUDIATION
'--- and/or (KEY_ENCIPHERMENT or KEY_AGREEMENT)
Const szOID_PKIX_KP_EMAIL_PROTECTION = "1.3.6.1.5.5.7.3.4"
Public Enum enmKeyUsage
KU_ClientAuthentication = 0
KU_EMailProtection = 1
KU_ServerAuthentication = 2
KU_CodeSigning = 3
End Enum
'-------------------------------------------------------------------
' Microsoft Enhanced Key Usage (Purpose) Object Identifiers
'-------------------------------------------------------------------
'--- Signer of CTLs
Const szOID_KP_CTL_USAGE_SIGNING = "1.3.6.1.4.1.311.10.3.1"
'--- Signer of TimeStamps
Const szOID_KP_TIME_STAMP_SIGNING = "1.3.6.1.4.1.311.10.3.2"
'-------------------------------------------------------------------
' Microsoft Attribute Object Identifiers
'-------------------------------------------------------------------
Const szOID_YESNO_TRUST_ATTR = "1.3.6.1.4.1.311.10.4.1"
'-- Properties
Private m_strUserName As String
Private m_strUnit As String
Private m_strOrganization As String
Private m_strCity As String
Private m_strState As String
Private m_strCountry As String
Private m_lReqID As Long
Private m_strServer As String
Private m_strCA As String
Private m_strEMail As String
Private m_lKeySpec As enmKeyType
Private m_strKeyUsage As String
Private m_lKeyUsage As enmKeyUsage
'-- Objects for Enrollment and Request controls
Private m_objXen As Object
Private m_objReq As CCertRequest
|
Creating the Class Properties
While going
through all the class declarations, you might have noticed a whole string of property variables. They hold pieces of information you need to include with the certificate request and hold the request ID. Next, you need to expose all these properties, as in Listing 5.2.
Code Listing 5.2.
clsCertRequest Class Properties
Public Property Get UserName() As String
UserName = m_strUserName
End Property
Public Property Let UserName(ByVal sNewValue As String)
m_strUserName = sNewValue
End Property
Public Property Get EMail() As String
EMail = m_strEMail
End Property
Public Property Let EMail(ByVal sNewValue As String)
m_strEMail = sNewValue
End Property
Public Property Get Unit() As String
Unit = m_strUnit
End Property
Public Property Let Unit(ByVal sNewValue As String)
m_strUnit = sNewValue
End Property
Public Property Get Organization() As String
Organization = m_strOrganization
End Property
Public Property Let Organization(ByVal sNewValue As String)
m_strOrganization = sNewValue
End Property
Public Property Get City() As String
City = m_strCity
End Property
Public Property Let City(ByVal sNewValue As String)
m_strCity = sNewValue
End Property
Public Property Get State() As String
State = m_strState
End Property
Public Property Let State(ByVal sNewValue As String)
m_strState = sNewValue
End Property
Public Property Get Country() As String
Country = m_strCountry
End Property
Public Property Let Country(ByVal sNewValue As String)
m_strCountry = sNewValue
End Property
Public Property Get RequestID() As Long
RequestID = m_lReqID
End Property
Public Property Get Server() As String
Server = m_strServer
End Property
Public Property Let Server(ByVal sNewValue As String)
m_strServer = sNewValue
End Property
Public Property Get CertificateAuthority() As String
CertificateAuthority = m_strCA
End Property
Public Property Let CertificateAuthority(ByVal sNewValue As String)
m_strCA = sNewValue
End Property
Public Property Get KeySpec() As enmKeyType
KeySpec = m_lKeySpec
End Property
Public Property Let KeySpec(ByVal lNewValue As enmKeyType)
m_lKeySpec = lNewValue
End Property
Public Property Get KeyUsage() As enmKeyUsage
KeyUsage = m_lKeyUsage
End Property
Public Property Let KeyUsage(ByVal lNewValue As enmKeyUsage)
'--- Set the key usage value
m_lKeyUsage = lNewValue
'--- Set the Key Usage OID to the appropriate string
Select Case m_lKeyUsage
Case KU_ClientAuthentication
m_strKeyUsage = szOID_PKIX_KP_CLIENT_AUTH
Case KU_EMailProtection
m_strKeyUsage = szOID_PKIX_KP_EMAIL_PROTECTION
Case KU_ServerAuthentication
m_strKeyUsage = szOID_PKIX_KP_SERVER_AUTH
Case KU_CodeSigning
m_strKeyUsage = szOID_PKIX_KP_CODE_SIGNING
End Select
End Property
|
In the KeyUsage
property in Listing 5.2, notice that you set not only the variable holding the enumerated value, but also a string value to the OID associated with the specified key usage. You use constants that you declared in Listing 5.1 for these OID values. It's important that these OID values are correct, so it doesn't hurt to go back and review them for correctness.
Class Initialization and Termination
Upon creating this
class, you need to create an instance of both the Certificate Enrollment Control and the Certificate Request object. You can keep these instances alive for the life of the class. You create the Certificate Enrollment Control by using the CreateObject
function, whereas the Certificate Request object can be instantiated with the New keyword, as in Listing 5.3.
Code Listing 5.3.
clsCertRequest Class Initialization
Private Sub Class_Initialize()
'*************************************************************
'* Written By: Davis Chapman
'* Date: September 12, 1999
'*
'* Syntax: Class_Initialize
'*
'* Parameters: None
'*
'* Purpose: During the class initialization, the instances of
'* the Certificate Enroll and Certificate Request
'* objects are created.
'*************************************************************
On Error GoTo InitErr
m_lReqID = 0
'--- Instantiate the certificate enrollment object
Set m_objXen = CreateObject("CEnroll.CEnroll.1")
'--- Instantiate the certificate request object
Set m_objReq = New CCertRequest
Exit Sub
InitErr:
'--- Tell the user about the error
MsgBox "Error creating request objects - " + Str(Err.Number) + _
" - " + Err.Description
End Sub
|
When the class is being
terminated, you'll want to free both of these resources by setting their variables to Nothing, as in Listing 5.4.
Code Listing 5.4.
clsCertRequest Class Termination
Private Sub Class_Terminate()
'--- Free the objects
Set m_objXen = Nothing
Set m_objReq = Nothing
End Sub
|
Requesting Certificates
Now it's
time to get down to action. The first real piece of meaty functionality that you can add to the class is the part that creates and submits the certificate request. In this functionality, you can build the distinguished names string from the information the user provides. Next, you can specify the key type that you want to generate and create a certificate request. When you have a certificate request, you can submit it to the CA and display the request status for the user. Once the request has been submitted, you can get the request ID for use at a later time. Finally, just for good measure, you can add some code that, if by some slim chance, the certificate was issued immediately, you can go ahead and get the certificate. To add all this functionality, add the code in Listing 5.5.
Code Listing 5.5. The DoCertRequest
Function
Public Function DoCertRequest() As Boolean
'*************************************************************
'* Written By: Davis Chapman
'* Date: September 12, 1999
'*
'* Syntax: DoCertRequest
'*
'* Parameters: None
'*
'* Purpose: This function builds the distinguished name string
'* and then calls the XEnroll control to generate a
'* public/private key pair and export the public key
'* in the form of a certificate request. The certificate
'* request is then submitted to the Certificate Authority
'* using the Certificate Request control.
'*************************************************************
DoCertRequest = False
Dim strDN As String
Dim strReq As String
Dim lDisp As Long
Dim strMsg As String
On Error GoTo DoCertReqErr
'--- Build the distinguished name string
strDN = "CN=" + m_strUserName _
+ ",OU=" + m_strUnit _
+ ",O=" + m_strOrganization _
+ ",L=" + m_strCity _
+ ",S=" + m_strState _
+ ",C=" + m_strCountry
'--- Do we have an E-Mail address?
If (m_strEMail <> "") Then
'--- Yes, so include it also
strDN = strDN + ",E=" + m_strEMail
End If
'--- Specify the key type
m_objXen.KeySpec = m_lKeySpec
'--- Create the PKCS #10 request
strReq = m_objXen.createPKCS10(strDN, m_strKeyUsage)
'--- Submit the request to the CA
lDisp = m_objReq.Submit(CR_IN_BASE64 Or CR_IN_PKCS10, _
strReq, _
"", _
m_strServer + "" + m_strCA)
'--- Get the textual description of the request status
strMsg = m_objReq.GetDispositionMessage
'--- Display it for the user
MsgBox strMsg, vbOKOnly, "Certificate Request"
'--- Did we fail? If so, then exit early
If ((lDisp = CR_DISP_INCOMPLETE) Or _
(lDisp = CR_DISP_ERROR) Or _
(lDisp = CR_DISP_DENIED)) Then Exit Function
'--- Get the request ID
m_lReqID = m_objReq.GetRequestId
'--- If the certificate has already been issued,
'--- then get the certificate
If (lDisp = CR_DISP_ISSUED) Or _
(lDisp = CR_DISP_ISSUED_OUT_OF_BAND) Then
If Not GetCert Then Exit Function
End If
'--- Didn't exit early, return TRUE
DoCertRequest = True
Exit Function
DoCertReqErr:
'--- Tell the user about the error
MsgBox "Error - " + Str(Err.Number) + " - " + Err.Description
End Function
|
Retrieving Certificates
The next piece
of functionality that you can add to your class is the retrieval of the issued certificate. In this function, you'll get the certificate from the server and then accept it into the certificate store. To add this functionality, add the code in Listing 5.6 to the clsCertRequest class.
Code Listing 5.6. The GetCert Function
Public Function GetCert() As Boolean
'*************************************************************
'* Written By: Davis Chapman
'* Date: September 12, 1999
'*
'* Syntax: GetCert
'*
'* Parameters: None
'*
'* Purpose: This function retrieves the issued certificate from
'* the CA using the Certificate Request control. Next,
'* the Certificate Enroll control is used to accept the
'* certificate and to place it into the certificate
'* store.
'*************************************************************
On Error GoTo GetCertErr
Dim strCert As String
'--- Initialize the return value
GetCert = False
'--- Get the certificate
strCert = m_objReq.GetCertificate(CR_OUT_BINARY Or CR_OUT_CHAIN)
'--- Accept the certificate into the certificate store
m_objXen.acceptPKCS7 strCert
'--- Didn't exit early, return TRUE
GetCert = True
Exit Function
GetCertErr:
'--- Tell the user about the error
MsgBox "Error - " + Str(Err.Number) + " - " + Err.Description
End Function
|
Checking on Request Status
The final piece
of functionality to be added to the clsCertRequest class is the checking of the request status. In this function, you'll need to retrieve the current status from the CA and display it for the user. If the certificate has already been issued, you'll call the GetCert function to retrieve the certificate. To add this functionality to the clsCertRequest class, add the code in Listing 5.7.
Code Listing 5.7. The GetReqStatus Function
Public Function GetReqStatus() As Long
'*************************************************************
'* Written By: Davis Chapman
'* Date: September 12, 1999
'*
'* Syntax: GetReqStatus
'*
'* Parameters: None
'*
'* Purpose: This function checks the current status of the
'* current outstanding certificate request. If the
'* request has been issued, then the certificate is
'* retrieved.
'*************************************************************
Dim lDisp As Long
Dim strMsg As String
'--- Retrieve the current status of our request
lDisp = m_objReq.RetrievePending(m_lReqID, _
m_strServer + "" + m_strCA)
'--- Get the textual description of the request status
strMsg = m_objReq.GetDispositionMessage
'--- Display it for the user
MsgBox strMsg, vbOKOnly, "Certificate Request"
'--- Set the status code to the return value
GetReqStatus = lDisp
'--- If the certificate has already been issued,
'--- then get the certificate
If (lDisp = CR_DISP_ISSUED) Or _
(lDisp = CR_DISP_ISSUED_OUT_OF_BAND) Then
If Not GetCert Then Exit Function
End If
End Function
|
Designing the Form
Now it's time
to turn your attention back to the application form. You need to gather a lot of information from the user of this utility, so you can add controls to the form as shown in Figure 5.4, setting their properties as specified in Table 5.10.
Table 5.10. Control Property Settings
Control
|
Property
|
Value
|
---|
Label
|
Caption
|
Ser&ver Name:
|
TextBox
|
Name
|
txtServer
|
Label
|
Caption
|
Certi&ficate Authority:
|
TextBox
|
Name
|
txtCA
|
Label
|
Caption
|
User &Name:
|
TextBox
|
Name
|
txtUserName
|
Label
|
Caption
|
Business &Unit:
|
TextBox
|
Name
|
txtUnit
|
Label
|
Caption
|
&Organization:
|
TextBox
|
Name
|
txtOrganization
|
Label
|
Caption
|
E-&Mail:
|
TextBox
|
Name
|
txtEMail
|
Label
|
Caption
|
&City:
|
TextBox
|
Name
|
txtCity
|
Label
|
Caption
|
&State:
|
TextBox
|
Name
|
txtState
|
Label
|
Caption
|
Countr&y:
|
TextBox
|
Name
|
txtCountry
|
Frame (Group Box)
|
Caption
|
Key Type
|
OptionButton
|
Name
|
optKeyExchange
|
|
Caption
|
Key &Exchange
|
OptionButton
|
Name
|
optSignature
|
|
Caption
|
Signa&ture
|
Frame (Group Box)
|
Caption
|
Key Usage
|
OptionButton
|
Name
|
optCliAuth
|
|
Caption
|
Client &Authentication
|
OptionButton
|
Name
|
optEMailProt
|
|
Caption
|
E-Mail &Protection
|
OptionButton
|
Name
|
optServAuth
|
|
Caption
|
Server Authent&ication
|
OptionButton
|
Name
|
optCodeSign
|
|
Caption
|
Co&de Signing
|
Command Button
|
Name
|
cmdReqCert
|
|
Caption
|
&Request Certificate
|
Command Button
|
Name
|
cmdCheckStat
|
|
Caption
|
Chec&k Status
|
Command Button
|
Name
|
cmdGetCert
|
|
Caption
|
&Get Certificate
|
Command Button
|
Name
|
cmdExit
|
|
Caption
|
E&xit
|
Form Initialization and Shutdown
In this application, it is easiest if you create a form-level instance of the Certificate Request class. To do so, add the code in Listing 5.8 to the form
declaration section.
Code Listing 5.8. Form Declarations
Option Explicit
Private crRequest As clsCertRequest
|
So that this instance of the Certificate Request class will be available throughout the life of the application, create it in the form load event, as in Listing 5.9.
Code Listing 5.9. The Form Load Event
Private Sub Form_Load()
'--- Create the Certificate Request object
Set crRequest = New clsCertRequest
End Sub
|
To clean up after yourself, you should destroy the Certificate Request class during the form unload event, as in Listing 5.10.
Code Listing 5.10. Form Termination
Private Sub Form_Unload(Cancel As Integer)
'--- Destroy the Certificate Request object
Set crRequest = Nothing
End Sub
|
To add the final piece of housekeeping code for the form, you need to close the application from the Exit button. To do so, add the code in Listing 5.11 to the Click event of the cmdExit button.
Code Listing 5.11. The cmdExit Click Event
Private Sub cmdExit_Click()
'--- Close this application
Unload Me
End Sub
|
Performing the Certificate Request
Now you're
ready to make the application submit a certificate request. You need to copy all the information the user has entered to the appropriate properties of the Certificate Request class. After all the information has been copied, you can call the class'DoCertRequest method, as in Listing 5.12.
Code Listing 5.12. The DoCertReq Subroutine
Private Sub DoCertReq()
'*************************************************************
'* Written By: Davis Chapman
'* Date: September 12, 1999
'*
'* Syntax: DoCertReq
'*
'* Parameters: None
'*
'* Purpose: This subroutine sets the values entered by the user
'* to the appropriate properties on the Certificate
'* Request object. Next, it calls the DoCertRequest
'* method in the class to make the certificate request.
'*************************************************************
'--- Set the certificate properties
crRequest.UserName = txtUserName
crRequest.Unit = txtUnit
crRequest.Organization = txtOrganization
crRequest.City = txtCity
crRequest.State = txtState
crRequest.Country = txtCountry
crRequest.EMail = txtEMail
'--- Specify what Certificate Authority to use
crRequest.Server = txtServer
crRequest.CertificateAuthority = txtCA
'--- Specify how the certificate and key will be used
If optSignature Then
crRequest.KeySpec = KT_Signature
Else
crRequest.KeySpec = KT_KeyExchange
End If
Select Case True
Case optCliAuth
crRequest.KeyUsage = KU_ClientAuthentication
Case optEMailProt
crRequest.KeyUsage = KU_EMailProtect
ion
Case optServAuth
crRequest.KeyUsage = KU_ServerAuthentication
Case optCodeSign
crRequest.KeyUsage = KU_CodeSigning
End Select
'--- Request the certificate
If crRequest.DoCertRequest Then
End If
End Sub
|
Now that you have the functionality to request the certificate, you need to trigger it from the Click event of the cmdReqCert button. To do so, add the code in Listing 5.13 to this event.
Code Listing 5.13. The cmdReqCert Click Event
Private Sub cmdReqCert_Click()
'--- Request a certificate
DoCertReq
End Sub
|
Checking the Status and Getting the Certificate
The rest of
the form functionality is fairly straightforward. You need to check the status of the request from the Click event of the cmdCheckStat button. This is a simple matter of calling the Certificate Request class'GetReqStatus method, as in Listing 5.14.
Code Listing 5.14. The cmdCheckStat Click Event
Private Sub cmdCheckStat_Click()
'--- Check the status of our certificate request
crRequest.GetReqStatus
End Sub
|
The final piece of functionality is to retrieve the certificate from the cmdGetCert button. Here, you need to call the class'GetCert method from the Click event, as in Listing 5.15.
Code Listing 5.15. The cmdGetCert Click Event
Private Sub cmdGetCert_Click()
'--- Get the certificate
crRequest.GetCert
End Sub
|
Running the Sample Application
Running this
sample application requires having access to Certificate Server. You need to be able to issue the certificate fairly quickly because you didn't build in any functionality to maintain the certificate request ID across sessions. Therefore, you have to keep the application running until the certificate has been issued.
When you first start the application, you should fill in all the information on the form. Remember that the country code can be only two characters long. In the first text box, you enter the network name of the computer that has Certificate Server running on it. The second text box is the name of the Certificate Authority as configured in Certificate Server. Fill in the rest of the information as you see fit, as shown in Figure 5.5.
After you submit the certificate request to the server (assuming that you encountered no errors), you need to open the Certification Authority utility on the Certificate Server machine. This utility manages the certificates issued by Certificate Server. In this utility, there should be a folder containing certificate requests, and your certificate request should be located in this list. Find it and approve it (this process is also known as resubmitting it), as shown in Figure 5.6. In Certificate Server 2 (shipped with Windows 2000), you can resubmit it by right-clicking over your certificate and selecting Resubmit from the list of tasks.
After the certificate has been issued, you can go back to your application and check the current status of the certificate. This action should automatically retrieve the certificate, placing it into your certificate store. To check, you can open the Properties dialog for Internet Explorer and then open the Certificates dialog from the Content tab. In the Certificates dialog, you should be able to find your new certificate in the Personal Certificates folder, as shown in Figure 5.7.