Building a Secure Messaging Utility

Now that you know how to export and import keys between users, it's time to see how you can make these processes work. To do so, you'll build a messaging application that can go through all the motions, performing the key creation, export, and import, all by itself, or with another copy of itself. To see this all working as it should, you will be able to run two copies of the application you are going to build on the same computer, but you really should run them on different machines connected over a network.

In the following sections, you'll learn how to do the following:

  • Modify the crypto class that you started in the preceding chapter, adding the capability to open or generate a public/private key pair, and export and exchange public and session keys.

  • Build an application form that can perform the key generation and exchange, both within itself and over a Winsock connection.

  • Run the application in both modes (standalone and networked) to see how the key exchange works.

Creating the Initial Project

You start by creating a new Standard EXE Visual Basic project. Name the project PubPrivKeys and the default form frmKeys. Copy the clsCrypto.cls file from the project directory of the last application from the preceding chapter into the project directory of this project, and then include the file in the project by selecting Project|Add Class Module.

Tip

If you skipped reading the preceding chapter, you should create a new class named clsCrypto and go back to the Chapter 3 examples, adding the code to this class from the two sample applications you'll find there. I recommend this approach over copying the class from the Web (http://www.samspublishing.com). By creating the class yourself, you'll understand what functionality is already contained within.


Making Additional Declarations

The first step is to turn your attention to making the necessary additions and alterations to the encryption class. After you've made these changes, you can turn your attention back to the default form.

Before you do anything else, you need to add a few more constant, function, and variable declarations to the crypto class. These new additions are provided in Listing 4.1. Remember, they are additions to the constants, functions, and variables that you already have declared in this class, so you will probably want to place them close to the other declarations, as best makes sense. I recommend placing these constant declarations at the bottom of the existing constant declarations, the function declarations near the end of the existing function declarations, and the variable declarations at the end of the entire declaration section.

Code Listing 4.1. New Constant, Function, and Variable Declarations
'exported key blob definitions
Private Const SIMPLEBLOB = &H1
Private Const PUBLICKEYBLOB = &H6
Private Const PRIVATEKEYBLOB = &H7
Private Const PLAINTEXTKEYBLOB = &H8

Private Const AT_KEYEXCHANGE = 1
Private Const AT_SIGNATURE = 2

'--- WinCrypt API Declarations
Private Declare Function CryptGenKey Lib "advapi32.dll" ( _
    ByVal hProv As Long, ByVal Algid As Long, _
    ByVal dwFlags As Long, phKey As Long) As Long

Private Declare Function CryptGetUserKey Lib "advapi32.dll" ( _
    ByVal hProv As Long, ByVal dwKeySpec As Long, _
    phUserKey As Long) As Long
Private Declare Function CryptExportKey Lib "advapi32.dll" ( _
    ByVal hKey As Long, ByVal hExpKey As Long, _
    ByVal dwBlobType As Long, ByVal dwFlags As Long, _
    ByVal pbData As String, pdwDataLen As Long) As Long

Private Declare Function CryptImportKey Lib "advapi32.dll" ( _
    ByVal hProv As Long, ByVal pbData As String, _
    ByVal dwDataLen As Long, ByVal hPubKey As Long, _
    ByVal dwFlags As Long, phKey As Long) As Long

'Private property buffers
Private m_sExportKey As String  'Used to publish the exported public
                                'key that can be sent to the other
                                'application that is to be sending
                                'encrypted data to this class

Private m_sImportKey As String  'Used to import the public key of
                                'the other application to which this
                                'application will be sending encrypted
                                'data

Private m_sSessionKey As String 'Used to publish and import the
                                'encrypted session key that is used
                                'to encrypt and decrypt the data to
                                'be passed between applications


'Private class-level variables
Private m_lHCryptKey As Long    'Handle for the AT_KEYEXCHANGE
                                '(Public/Private) key for this
                                'application

Private m_lHImportKey As Long   'Handle for the imported public
                                'key of the application to which
                                'this application will be sending
                                'data
					

Adding New Properties

Now you can add three new properties to the crypto class. They are properties for the import and export keys, as well as the session key. These properties will make the exchange of keys very accessible to the application form that will be performing the actual exchange of keys. To add these properties, add the code in Listing 4.2 to the crypto class.

Code Listing 4.2. New Public Properties
Public Property Get ExportKey() As String
    ExportKey = m_sExportKey
End Property

Public Property Get SessionKey() As String
    SessionKey = m_sSessionKey
End Property

Public Property Let SessionKey(vNewValue As String)
    m_sSessionKey = vNewValue
End Property

Public Property Get ImportKey() As String
    ImportKey = m_sImportKey
End Property

Public Property Let ImportKey(vNewValue As String)
    m_sImportKey = vNewValue
End Property
					

Getting the User Public/Private Key Pair

The first piece of functionality that you need to add to the crypto class is the capability to open or generate a Key Exchange public/private key pair for the current user. To add this functionality, add the code in Listing 4.3 to the crypto class.

Code Listing 4.3. The GetExchangeKeypair Function
Public Function GetExchangeKeypair() As Boolean
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 06, 1999
'*
'* Syntax:     GetExchangeKeypair
'*
'* Parameters:  None
'*
'* Purpose: This function loads the AT_KEYEXCHANGE keyset.
'*          If the keyset or key container does not exist,
'*          then this function creates them.
'*************************************************************
    Dim lResult As Long
    Dim sResult As String
    GetExchangeKeypair = False

    '--- Attempt to get handle to exchange key
    If Not CBool(CryptGetUserKey(m_lHCryptProv, AT_KEYEXCHANGE, _
            m_lHCryptKey)) Then

        '--- Determine what the error was
        lResult = Err.LastDllError

        '--- If the problem is that there is no existing key,
        '--- generate one
        If ((lResult = NTE_NO_KEY) Or (lResult = 0)) Then
            If Not CBool(CryptGenKey(m_lHCryptProv, AT_KEYEXCHANGE, _
                    0, m_lHCryptKey)) Then
                m_sErrorMsg = "Error during CryptGenKey - " & _
                        CStr(Err.LastDllError)
                MsgBox m_sErrorMsg, vbOKOnly, "VB Crypto"
                Exit Function
            End If
        Else

            '--- If there was another cause for the error, inform
            '--- the user what the problem is
            Select Case lResult
                Case ERROR_INVALID_HANDLE
                    sResult = "Invalid handle"
                Case ERROR_INVALID_PARAMETER
                    sResult = "Invalid parameter"
                Case NTE_BAD_KEY
                    sResult = "Bad key"
                Case NTE_BAD_UID
                    sResult = "Bad handle"
            End Select
            m_sErrorMsg = "Error during CryptGetUserKey - " + sResult
            MsgBox m_sErrorMsg, vbOKOnly, "VB Crypto"
            Exit Function
        End If
    End If

    '--- Didn't exit early, return TRUE
    GetExchangeKeypair = True
End Function
					

In this function, you first try to open an existing key pair in the key store by using the CryptGetUserKey function. If this function fails because no key exists, then you create the key pair by using the CryptGenKey function.

Tip

In Listing 4.3, notice that the Crypto API error conditions are checked using the LastDLLError property of the Err object. This is the recommended way of checking for API function error conditions. Many VB programmers map the GetLastError API function to try to get the error condition codes. This approach is wrong because the VB runtime environment usually resets the error condition to ERROR_SUCCESS or NO_ERROR (both 0). VB does make the error condition code available through the LastDLLError property of the Err object, and this copy of the error code does not get reset, enabling you to check it to discover what went wrong.


Exporting the Public Key

The next piece of functionality you need to add is the capability to export the public key from the Key Exchange key pair. The exported key will be made available through the ExportKey property. To add this functionality to the crypt class, add the function in Listing 4.4.

Code Listing 4.4. The ExportPublicKey Function
Public Function ExportPublicKey() As Boolean
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 06, 1999
'*
'* Syntax:     ExportPublicKey
'*
'* Parameters:  None
'*
'* Purpose: This function exports the public key from the
'*          AT_KEYEXCHANGE keyset, placing the exported key
'*          into the ExportKey property
'*************************************************************
On Error Resume Next
    ExportPublicKey = False

    Dim lDataSize As Long
    Dim lResult As Long

    '--- Null the target string, so we can get the size of the string
    '--- that will be exported
    lDataSize = 0
    '--- Call CryptExportKey with the nulled string. This will give us
    '--- the required string size that will be needed to hold the 
    '--- exported key
    lResult = CryptExportKey(m_lHCryptKey, 0, PUBLICKEYBLOB, 0, _
            vbNullString, lDataSize)

    '--- Size the target string for the size specified, filling it with
    '--- null characters
    m_sExportKey = String(lDataSize, vbNullChar)

    '--- Export the public key
    If Not CBool(CryptExportKey(m_lHCryptKey, 0, PUBLICKEYBLOB, 0, _
            m_sExportKey, lDataSize)) Then
        m_sErrorMsg = "Error during CryptExportKey - " & _
                CStr(Err.LastDllError) & Error$
        MsgBox m_sErrorMsg, vbOKOnly, "VB Crypto"
        Exit Function
    End If
						
    '--- Didn't exit early, return TRUE
    ExportPublicKey = True
    Exit Function
End Function
					

Here, you initially substituted a NULL string (vbNullString) for the export key property and set the size to zero (0) for the initial call to CryptExportKey. By doing so, you place the size of the string needed into the lDataSize variable. You can take that size and create the string buffer of the correct length before calling CryptExportKey a second time. You also export the public key directly to the ExportKey property variable.

Importing the Public Key

Next, you need to add the corresponding functionality to import the public key of the other user. In this function, you can assume that the exported key has been placed into the ImportKey property. You need to get the length of the exported key and then call the CryptImportKey function to import the key. Doing so gives you a handle to a key object containing the public key of the other user. To add this functionality to the crypto class, add the function in Listing 4.5.

Code Listing 4.5. The ImportPublicKey Function
Public Function ImportPublicKey() As Boolean
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 06, 1999
'*
'* Syntax:     ImportPublicKey
'*
'* Parameters:  None
'*
'* Purpose: This will import the public key of the other
'*          application, taking it from the ImportKey property.
'*          The imported public key will be held in the
'*          m_lHImportKey class variable.
'*************************************************************
On Error Resume Next
    ImportPublicKey = False

    Dim lDataSize As Long

    '--- Get the size of the key being imported
    lDataSize = Len(m_sImportKey)

    '--- Import the key, storing it in the m_lHImportKey class variable
    If Not CBool(CryptImportKey(m_lHCryptProv, m_sImportKey, _
            lDataSize, 0, 0, m_lHImportKey)) Then
        m_sErrorMsg = "Error during CryptImportKey - " & _
                CStr(Err.LastDllError) & Error$
        MsgBox m_sErrorMsg, vbOKOnly, "VB Crypto"
        Exit Function
    End If

    '--- Didn't exit early, return TRUE
    ImportPublicKey = True
    Exit Function
End Function
					

Creating and Exporting the Session Key

Now that you can export and import a public key, you need to be able to create a session key and export it, using the public key to encrypt it. To add this functionality, add the code in Listing 4.6 to the crypto class.

Code Listing 4.6. The CreateAndExportSessionKey Function
Public Function CreateAndExportSessionKey() As Boolean
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 07, 1999
'*
'* Syntax:     CreateAndExportSessionKey
'*
'* Parameters:  None
'*
'* Purpose: This will generate a session key and export it,
'*          encrypting the exported key using the public key
'*          of the recipient. The exported session key will
'*          be placed in the SessionKey property.
'*************************************************************
On Error Resume Next
    CreateAndExportSessionKey = False

    Dim lDataSize As Long
    Dim lResult As Long
    Dim sCryptBuffer As String
    Dim lCryptLength As Long
    Dim lCryptBufLen As Long

    '--- Create a random block cipher session key.
    If Not CBool(CryptGenKey(m_lHCryptProv, CALG_RC2, _
            CRYPT_EXPORTABLE, m_lHSessionKey)) Then
        m_sErrorMsg = "Error " & CStr(Err.LastDllError) & _
                " during CryptGenKey!"
        MsgBox m_sErrorMsg, vbOKOnly, "VB Crypto"
        Exit Function
    End If

    '--- Determine the size of the key blob and allocate memory.
    lDataSize = 0
    lResult = CryptExportKey(m_lHSessionKey, m_lHImportKey, _
            SIMPLEBLOB, 0, vbNullString, lDataSize)
    m_sSessionKey = String(lDataSize, vbNullChar)

    '--- Export the session key, using the imported public key to 
    '--- encrypt it
    If Not CBool(CryptExportKey(m_lHSessionKey, m_lHImportKey, _
            SIMPLEBLOB, 0, m_sSessionKey, lDataSize)) Then
        m_sErrorMsg = "Error during CryptExportKey - " & _
                CStr(Err.LastDllError) & Error$
        MsgBox m_sErrorMsg, vbOKOnly, "VB Crypto"
        Exit Function
    End If

    '--- Didn't exit early, return TRUE
    CreateAndExportSessionKey = True
End Function
					

The first thing you do in this function is create an exportable session key. After you generate the session key, you substitute the NULL string (vbNullString) for the session key property and specify the length as zero (0). This way, you can call CryptExportKey, specifying to export the session key and to encrypt it using the public key, so that you can get the buffer size you need. When you have the correct buffer size, you size the session key property variable and call CryptExportKey again, using the same syntax, to actually export the encrypted session key directly to the session key property variable. You can now take the exported session key and send it to the recipient, knowing that the recipient is the only person who can decrypt and import the session key using his or her private key.

Importing the Session Key

Next, you need to be able to import the exported session key so that you can exchange encrypted messages with the other user. This process is almost like importing the public key; this time, though, you need to provide the handle to the public/private key pair so that the CSP knows to use the private key to decrypt the session key while importing it. To add this functionality to the crypto class, add the code in Listing 4.7.

Code Listing 4.7. The ImportSessionKey Function
Public Function ImportSessionKey() As Boolean
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 07, 1999
'*
'* Syntax:     ImportSessionKey
'*
'* Parameters:  None
'*
'* Purpose: This will import the session key from the SessionKey
'*          property.
'*************************************************************
On Error Resume Next
    ImportSessionKey = False

    Dim lDataSize As Long
    Dim lResult As Long
    Dim sCryptBuffer As String
    Dim lCryptLength As Long
    Dim lCryptBufLen As Long

    '--- Determine the size of the session key to be imported
    lDataSize = Len(m_sSessionKey)
    '--- Import the session key. As it should have been encrypted using
    '--- our public key, the CAPI should use our private key to
    '--- decrypt the session key during the import
    If Not CBool(CryptImportKey(m_lHCryptProv, m_sSessionKey, _
            lDataSize, m_lHCryptKey, 0, m_lHSessionKey)) Then
        m_sErrorMsg = "Error during CryptImportKey - " & _
                CStr(Err.LastDllError) & Error$
        MsgBox m_sErrorMsg, vbOKOnly, "VB Crypto"
        Exit Function
    End If

    '--- Didn't exit early, return TRUE
    ImportSessionKey = True
End Function
					

Terminating the Class

The last update that you need to make to the crypto class is in the termination event of the class. You may have multiple keys open when the class is terminated, so you need to check each one to see whether you have a valid handle, and destroy each one for which you do have a valid handle. Update the Class_Terminate event for the crypto class, adding the bolded code shown in Listing 4.8.

Code Listing 4.8. The Updated Class_Terminate Event
Private Sub Class_Terminate()
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 06, 1999
'*
'* Syntax:     Class_Terminate
'*
'* Parameters:  None
'*
'* Purpose: This is executed when this class is destroyed. This
'*          subroutine examines each of the class-level handles
'*          for the keys and keycontext, and destroys or
'*          releases them as is appropriate to clean up after
'*          oneself.
'*************************************************************
    Dim lResult As Long
   '--- Do we have an imported key? If so, destroy it.
						   If (m_lHImportKey) Then lResult = CryptDestroyKey(m_lHImportKey)
						    '--- Do we have a Public/Private key pair? If so, destroy it
						    If (m_lHCryptKey) Then lResult = CryptDestroyKey(m_lHCryptKey)

    '--- Do we have an open key context? If so, release it.
    If (m_lHCryptProv) Then _
            lResult = CryptReleaseContext(m_lHCryptProv, 0)
End Sub
					

Designing the User Interface

Now, to make this a working application, you need to turn your attention back to the default form, adding the controls and code to make the whole thing work. Because this application will work over a network using the Winsock control, you need to add it to the project. To add the Winsock control, select Project, Components to open the Components dialog. Select the Microsoft Winsock Control from the list of controls, making sure that its check box is selected, as shown in Figure 4.1. Then click OK to add this control to your palette.

Figure 4.1. Including the Winsock control.


Now that you've added the Winsock control, add the various controls to the form as shown in Figure 4.2, setting the properties of the controls as listed in Table 4.10. The list of control set tings in Table 4.10 are provided in left-to-right, top-to-bottom order (more or less).

Figure 4.2. Application screen layout.


Table 4.10. Control Property Settings
Control Property Value
Frame (Group Box) Name frRole
 Caption Socket Type
OptionButton Name optClient
 Caption &Client
OptionButton Name optServer
 Caption &Server
Label Caption Server &Name:
TextBox Name txtServerName
Label Caption Server &Port:
TextBox Name txtPort
Command Button Name cmdListen
 Caption &Listen
Command Button Name cmdClose
 Caption &Close
 Enabled False
Label Caption &Message:
TextBox Name txtMessage
Label Caption Received Data:
TextBox Name txtRecvdData
Label Caption Encrypted Msg:
TextBox Name txtEncryptMsg
Label Caption Decrypted Msg:
TextBox Name txtDecryptMsg
Winsock Name wsSocket
Command Button Name cmdGenKeys
 Caption &Generate and Export Keys
Command Button Name cmdImportPublicKey
 Caption I&mport Public
  Key and Generate
  Session Key
 Enabled False
Command Button Name cmdImportSessionKey
 Caption &Import
  Session Key
 Enabled False
Command Button Name cmdSendMsg
 Caption Sen&d Message
 Enabled False
Command Button Name cmdExit
 Caption E&xit

Performing Form Initialization, Cleanup, and Other Miscellaneous Functions

Before you get to the good stuff, you need to take care of some details. First, you need to declare a few form-level variables to use in your code. Add these variables to the form, as shown in Listing 4.9.

Code Listing 4.9. Form Variable Declarations
Option Explicit

Private m_clsCrypto As clsCrypto
Private m_strPublicKey As String
Private m_strSessionKey As String
					

In the applications that you built in the preceding chapter, you used local variables for the crypto class object when you were performing the encryption or decryption. Because of the different nature of this application, you are going to have an instance of the crypto class object in existence for the entire life of the application. Therefore, you need to create an instance of the crypto class during the form loading and destroy it during the form unloading. To add this functionality, add the code in Listing 4.10 for the form Load and Unload events.

Code Listing 4.10. Form Initialization and Cleanup
Private Sub Form_Load()
    '-- Create the instance of the crypto class
    Set m_clsCrypto = New clsCrypto
End Sub

Private Sub Form_Unload(Cancel As Integer)
    '-- Destroy the instance of the crypto class
    Set m_clsCrypto = Nothing
End Sub
					

Because this same application needs to operate in both Client and Server modes (when using Winsock for network communications), you provide the radio buttons for selecting which mode each instance is operating in. Each side needs to change what is done to initiate the connection between the two. Because of these differences in functionality, you can change the label on the button you will be using to make the connection to reflect that change. When the application is in Server mode, you can label the button "Listen," and when the application is in Client mode, you label the button "Connect." To add this functionality, add the code in Listing 4.11 to the Click events of the two radio buttons.

Code Listing 4.11. Application Operation Mode Switching
Private Sub optClient_Click()
    '-- Client mode
    cmdListen.Caption = "Connec&t"
    Me.Caption = "Public/Private Keys - Client"
End Sub
Private Sub optServer_Click()
    '-- Server mode
    cmdListen.Caption = "&Listen"
    Me.Caption = "Public/Private Keys - Server"
End Sub
					

When you're closing the application, you need to check whether you have created a session key. If so, you need to destroy it before exiting. To add this functionality, add the code in Listing 4.12 to the Click event of the cmdExit button.

Code Listing 4.12. The cmdExit_Click Event
Private Sub cmdExit_Click()
    '-- Did we create or receive a session key?
    If m_strSessionKey <> "" Then

        '-- If so, then destroy it
        m_clsCrypto.DestroySessionKey
    End If

    '-- Close the application
    Unload Me
End Sub
					

Yes, you could have just included the session key in the crypto class cleanup, but this is a more realistic way of handling the session cleanup. Whenever a session is ended, either by closing the connection or other means, the session key should be destroyed. You'll see later that you'll add this same functionality to the closing of the Winsock connection.

Performing the Initial Server Key Exchange

You can perform the key exchange in three steps. The first step is performed by the server, opening or creating the public/private key pair, exporting the public key, and sending it to the client. During the second step, the client imports the server's public key, generates a session key, and exports it using the server's public key to encrypt it. In the third and final step, the server imports the session key, decrypting it using the server's private key.

To start this process, you need to create a subroutine that controls the initial step of the process. To add this functionality, add the code in Listing 4.13 to the form.

Code Listing 4.13. The DoServerKeyExchange Subroutine
Private Sub DoServerKeyExchange()
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 09, 1999
'*
'* Syntax:     DoServerKeyExchange
'*
'* Parameters:  None
'*
'* Purpose: The initial server portion of the key exchange
'*          consists of retrieving or creating a Public/Private
'*          key pair for the server. The public key is exported
'*          for sending to the client. Finally, the public key
'*          is sent to the client.
'*************************************************************
    '-- Continue until an error occurs
    '-- Generate or get the public/private key pair
    If Not m_clsCrypto.GetExchangeKeypair Then _
        Exit Sub

    '-- Export the public key
    If Not m_clsCrypto.ExportPublicKey Then _
        Exit Sub

        '-- Send the public key to the client
        m_strPublicKey = m_clsCrypto.ExportKey
        If wsSocket.State = sckConnected Then
            wsSocket.SendData m_strPublicKey
        Else
            cmdImportPublicKey.Enabled = True
        End If
        cmdGenKeys.Enabled = False
End Sub
					

In this subroutine, you either open or generate the Key Exchange key pair and then export the public key, saving it into the form-level variable. Finally, you check to see whether you have an active Winsock connection. If you do, you send the public key to the client. If you are not connected, you can assume that you are running in standalone mode and just need to enable the button to perform the next step in the key exchange.

To trigger this first step in the key exchange, you need to attach the code in Listing 4.14 to the Click event of the cmdGenKeys button. This button will be used to start the key exchange process when running in standalone mode.

Code Listing 4.14. The cmdGenKeys_Click Event
Private Sub cmdGenKeys_Click()
    '-- Perform the initial server portion of the key exchange
    DoServerKeyExchange
End Sub
					

Performing the Client Key Exchange

For the second step in the key exchange, you need to copy the public key from the form variable to the ImportKey property of the crypto class and call the ImportPublicKey method. After you import the public key, you need to generate and export a session key. Finally, you need to check whether you have an open Winsock connection. If you do, you need to send the exported session key to the server. If not, you just need to enable the cmdImportSessionKey button to trigger the final step in the key exchange when in standalone mode. To add this functionality, add the subroutine in Listing 4.15 to the form.

Code Listing 4.15. The DoClientKeyExchange Subroutine
Private Sub DoClientKeyExchange()
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 09, 1999
'*
'* Syntax:     DoClientKeyExchange
'*
'* Parameters:  None
'*
'* Purpose: The client portion of the key exchange starts by
'*          importing the server's public key. Next, a session
'*          key is generated, encrypted using the server's
'*          public key (ensuring that it can only be decrypted
'*          with the server's private key), and exported and
'*          sent to the server.
'*************************************************************
    '-- Copy the public key to the Crypto object
    m_clsCrypto.ImportKey = m_strPublicKey

    '-- Continue until an error occurs
    '-- Import the server's public key
    If Not m_clsCrypto.ImportPublicKey Then _
        Exit Sub

    '-- Create and export a session key.
    '--- The session key is encrypted using the
    '--- server's public key during the export.
    '--- This ensures that only the server can
    '--- import and use this session key using
    '--- its private key.
    If Not m_clsCrypto.CreateAndExportSessionKey Then _
        Exit Sub

        '-- Send the session key to the client
        m_strSessionKey = m_clsCrypto.SessionKey
        If wsSocket.State = sckConnected Then
            wsSocket.SendData m_strSessionKey
            cmdSendMsg.Enabled = True
        Else
            cmdImportSessionKey.Enabled = True
        End If
        cmdImportPublicKey.Enabled = False
End Sub
					

Now you need to be able to trigger this second step. You can do so from the Click event of the cmdImportPublicKey button, as shown in Listing 4.16.

Code Listing 4.16. The cmdImportPublicKey_Click Event
Private Sub cmdImportPublicKey_Click()
    '-- Perform the client portion of the key exchange
    DoClientKeyExchange
End Sub
					

Finishing the Server Key Exchange

To finish the key exchange, you need to import the session key into the crypto class object. At this point, you are ready to start exchanging messages. To add this functionality, add the subroutine in Listing 4.17 to the form.

Code Listing 4.17. The FinishServerKeyExchange Subroutine
Private Sub FinishServerKeyExchange()
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 09, 1999
'*
'* Syntax:     FinishServerKeyExchange
'*
'* Parameters:  None
'*
'* Purpose: The server completes the key exchange by importing
'*          and decrypting the session key. Once this is done,
 '*          both applications are ready to start sending messages
'*          to each other.
'*************************************************************
    '-- Copy the session key to the Crypto object
    m_clsCrypto.SessionKey = m_strSessionKey

    '-- Import and decrypt the session key
    If m_clsCrypto.ImportSessionKey Then

        '-- We are now ready to send messages back and forth
        cmdSendMsg.Enabled = True
        cmdImportSessionKey.Enabled = False
    End If
End Sub
					

To trigger this last step while you're in standalone mode, call the subroutine you just created from the Click event of the cmdImportSessionKey button, as in Listing 4.18.

Code Listing 4.18. The cmdImportSessionKey_Click Event
Private Sub cmdImportSessionKey_Click()
    '-- Perform the final server portion of the key exchange
    FinishServerKeyExchange
End Sub
					

Sending and Receiving Encrypted Messages

Now that you've completed the key exchange, you can send messages back and forth using the session key to encrypt and decrypt them. You send and receive messages much like you did in the applications that you built in the preceding chapter. First, you take the original message, copy it to the input buffer of the crypto object, encrypt the message, and then copy the encrypted message from the output buffer of the crypto object. At this point, if you have a Winsock connection, you send the encrypted message to the other user. If you are running in standalone mode, you can automatically trigger the message decryption process. To add this functionality, add the subroutine in Listing 4.19 to the form.

Code Listing 4.19. The SendEncryptedMsg Subroutine
Private Sub SendEncryptedMsg()
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 09, 1999
'*
'* Syntax:     SendEncryptedMsg
'*
 '* Parameters:  None
'*
'* Purpose: When sending a message, we need to encrypt it using
'*          the session key. Once the message has been encrypted,
'*          we can send it to the other application.
'*************************************************************
    '-- Copy the message to the Crypto object
    m_clsCrypto.InBuffer = txtMessage.Text

    '-- Encrypt the message using the session key
    If m_clsCrypto.EncryptMessageData Then

        '-- Display the encrypted message
        txtEncryptMsg.Text = m_clsCrypto.OutBuffer

        '-- Are we connected to another application?
        If wsSocket.State = sckConnected Then

            '-- Yes, so send the encrypted message
            wsSocket.SendData txtEncryptMsg.Text
        Else

            '-- No, Decrypt the message and display
            '-- the results
            ReceiveEncryptedMsg
        End If
    End If
End Sub
					

The message decryption functionality is basically the same as the encryption functionality. You copy the encrypted message to the input buffer of the crypto object, call the decryption method, and then copy the decrypted message from the output buffer of the crypto object. To add this functionality to the form, add the subroutine in Listing 4.20 to the form.

Code Listing 4.20. The ReceiveEncryptedMsg Subroutine
Private Sub ReceiveEncryptedMsg()
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 09, 1999
'*
'* Syntax:     ReceiveEncryptedMsg
'*
'* Parameters:  None
'*
 '* Purpose: When an encrypted message is received, it is
'*          decrypted using the session key.
'*************************************************************
    '-- Copy the encrypted message to the Crypto object
    m_clsCrypto.InBuffer = txtEncryptMsg.Text

    '-- Decrypt the message using the session key
    If m_clsCrypto.DecryptMessageData Then

        '-- Display the decrypted message
        txtDecryptMsg.Text = m_clsCrypto.OutBuffer
    End If
End Sub
					

Finally, you need to be able to trigger the message sending at will. To do so, you call the SendEncryptedMsg subroutine from the Click event of the cmdSendMsg button, as in Listing 4.21.

Code Listing 4.21. The cmdSendMsg_Click Event
Private Sub cmdSendMsg_Click()
    '-- Encrypt and send the message
    SendEncryptedMsg
End Sub

At this point, you should be able to run the sample application in standalone mode, using the sequence of command buttons to walk through each step of the key exchange, and then encrypt and decrypt messages using the session key, as shown in Figure 4.3.

Figure 4.3. Stepping through the key exchange in standalone mode.


Listening for Connection RequestsNow that you have the standalone functionality working, you need to add the Winsock functionality to network-enable this application. You can start by placing the socket into Listen mode for the server functionality. Before placing the server socket into Listen mode, though, you need to set the port on which it needs to listen for connection requests. To add this functionality, add the subroutine in Listing 4.22 to the form.

Note

If you do not have experience working with Winsock communications, or don't understand how it works and want a thorough explanation of the topic, you might want to pick up Sams Visual Basic 6 Unleashed, which provides an in-depth look at Winsock and other Visual Basic topics.


Code Listing 4.22. The StartListen Subroutine
Private Sub StartListen()
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 09, 1999
'*
'* Syntax:     StartListen
'*
'* Parameters:  None
'*
'* Purpose: This subroutine checks to see that a number has
'*          been entered in the port text box. If one has, the
'*          socket is configured to use that port, and is placed
'*          in "Listen" mode.
'*************************************************************
    '-- Do we have a valid value for the port to listen on?
    If IsNumeric(txtPort) Then

        '-- Yes, don't allow the mode to be changed
        frRole.Enabled = False
        cmdListen.Enabled = False

        '-- Set the port on the socket
        wsSocket.LocalPort = CLng(txtPort)

        '-- Start the socket listening for connection requests
        wsSocket.Listen
    Else
        '-- No port specified, tell the user
        MsgBox "You must specify a port on which to listen."
    End If
End Sub
					

Connecting to the Server

You also need to add the client portion of the socket connection initialization. For this, you need to tell the Winsock control not just the port to connect to, but also the computer on which the server is running. After you set all this information on the Winsock control, you can call its Connect method to connect to the server. To add this functionality to the application, add the ConnectToServer subroutine in Listing 4.23 to the form.

Code Listing 4.23. The ConnectToServer Subroutine
Private Sub ConnectToServer()
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 09, 1999
'*
'* Syntax:     ConnectToServer
'*
'* Parameters:  None
'*
'* Purpose: This subroutine checks to see that a number has
'*          been entered in the port text box, and a server name
'*          has been entered in the server text box. If one has, the
'*          socket is configured to connect to that port and server,
'*          and the "Connect" method is called.
'*************************************************************
    '-- Do we have a server name to connect to?
    If txtServerName <> "" Then

        '-- Do we have a valid value for the port to connect to?
        If IsNumeric(txtPort) Then

            '-- Yes, don't allow the mode to be changed
            frRole.Enabled = False
            cmdListen.Enabled = False

            '-- Tell the socket what server and port to connect to
            wsSocket.RemotePort = CLng(txtPort)
            wsSocket.RemoteHost = txtServerName
            wsSocket.LocalPort = 0
            '-- Open the connection
            wsSocket.Connect
        Else
						
            '-- No port specified, tell the user
            MsgBox "You must specify a port to connect."
        End If
    Else

        '-- No server specified, tell the user
        MsgBox "You must specify a server name or IP address."
    End If
End Sub
					

Now that you've built the functionality to start the connection process for both the Server and Client modes, you need to trigger the appropriate set of functionality. You can do so from the Click event of the cmdListen button. You can check which mode you're running in and call the appropriate subroutine, as in Listing 4.24.

Code Listing 4.24. The cmdListen_Click Event
Private Sub cmdListen_Click()
    '-- Are we in client or server mode?
    If optClient Then

        '-- Client mode, open a connection to the server
        ConnectToServer
    Else

        '-- Server mode, start listening for connection requests
        StartListen
    End If
End Sub
					

Receiving the Connection Request

You need to add one more step to complete the connection process. You need to add the code to complete the connection once the ConnectionRequest event is triggered on the server. First, you need to close the listening socket and then accept the incoming connection request. After you establish the connection, you can trigger the first part of the key exchange process. To add this functionality, add the code in Listing 4.25 to the ConnectionRequest event of the Winsock control.

Code Listing 4.25. The wsSocket_ConnectionRequest Event
Private Sub wsSocket_ConnectionRequest(ByVal requestID As Long)
    '-- Close the socket from listen mode
    If wsSocket.State <> sckClosed Then wsSocket.Close

    '-- Accept the connection request
    wsSocket.Accept requestID

    '-- Connection established, set the socket connection buttons.
    cmdClose.Enabled = True

    '-- Perform the initial server portion of the key exchange
    DoServerKeyExchange
End Sub
					

After you make the connection, you need to do a little housekeeping on the client. On the Connect event of the Winsock control, add the code in Listing 4.26 to enable and disable the appropriate command buttons on the form.

Code Listing 4.26. The wsSocket_Connect Event
Private Sub wsSocket_Connect()
    '-- Connection established, set the socket connection buttons.
    cmdListen.Enabled = False
    cmdClose.Enabled = True
End Sub
					

Handling Data Arrival

When you have an open Winsock connection, everything is triggered by the DataArrival event. As you receive data, you need to determine what data you are receiving and what you need to do with it. You can do so initially by checking the form variables to determine what you do and do not have. If you do not have anything in the public key variable, you can make the assumption that you are receiving the public key and that you need to trigger the second step in the key exchange process. If you don't have anything in the session key variable, you can assume that you are receiving the session key and need to trigger the final step in the key exchange. If you have something in both of these form variables, you must be receiving encrypted messages that you just need to decrypt and display for the user. To add this functionality to the form, add the code in Listing 4.27 to the DataArrival event of the Winsock control.

Code Listing 4.27. The wsSocket_DataArrival Event
Private Sub wsSocket_DataArrival(ByVal bytesTotal As Long)
'*************************************************************
'* Written By: Davis Chapman
'* Date:       September 09, 1999
'*
'* Event:     wsSocket_DataArrival
'*
'* Description: When data is received, we have to determine
'*              what the data might be, and what we need to
'*              do with it. Since this is implementing a very
'*              raw protocol, we can take a survey of what we
'*              have and have not received or generated to
'*              determine what we need to do with what we've
'*              received. If we have no public key, then we
'*              are the client and are receiving the public
'*              key for the server. If we have no session key
'*              then we must be the server receiving the client
'*              generated session key that has been encrypted
'*              using our public key. Otherwise, this is message
'*              data that just needs to be decrypted and displayed.
'*************************************************************
    Dim strData As String

    '-- Retrieve the data that has arrived
    wsSocket.GetData strData

    '-- Show it to the user
    txtRecvdData.Text = strData

    '-- Do we have a public key yet?
    If m_strPublicKey = "" Then

        '-- No, then this must be the public key of the
        '-- server that we are receiving
        m_strPublicKey = strData

        '-- Perform the client portion of the key exchange
        DoClientKeyExchange
        cmdGenKeys.Enabled = False
    Else
        '-- Do we have a session key yet?
        If m_strSessionKey = "" Then

            '-- No, then this must be the session key from the
            '-- client that we are receiving
            m_strSessionKey = strData
            '-- Perform the final server portion of the key exchange
            FinishServerKeyExchange
        Else

            '-- All keys have been exchanged, this is message data
            '-- that we are receiving
            txtEncryptMsg.Text = strData

            '-- Perform the message decryption
            ReceiveEncryptedMsg
        End If
    End If
End Sub
					

Closing the Socket Connection

To wrap up the application, you need to add the code to close the Winsock connection and to handle the closing of the Winsock connection. You can start with how the closing of the Winsock connection is triggered. When the user clicks the cmdClose button on either application, you should close the connection and clear out the session key. You also should reset the socket connection buttons so that the connection can be reinitialized. To add this functionality, add the code in Listing 4.28 to the Click event of the cmdClose button.

Code Listing 4.28. The cmdClose_Click Event
Private Sub cmdClose_Click()
    '-- If the socket is not closed, close it
    If wsSocket.State <> sckClosed Then wsSocket.Close

    '-- Did we create or receive a session key?
    If m_strSessionKey <> "" Then

        '-- If so, then destroy it
        m_clsCrypto.DestroySessionKey
        m_strSessionKey = ""
    End If

    '-- Reset the socket connection buttons
    cmdListen.Enabled = True
    cmdClose.Enabled = False
End Sub
					

When one of the two applications closes the connection, the Close event is triggered in the other application. In this event, you need to perform the same steps as you did in the one that started the closing of the connection. To add this functionality, add the code in Listing 4.29 to the Close event of the Winsock control.

Code Listing 4.29. The wsSocket_Close Event
Private Sub wsSocket_Close()
    '-- If the socket is not closed, close it
    If wsSocket.State <> sckClosed Then wsSocket.Close

    '-- Did we create or receive a session key?
    If m_strSessionKey <> "" Then

        '-- If so, then destroy it
        m_clsCrypto.DestroySessionKey
        m_strSessionKey = ""
    End If

    '-- Reset the socket connection buttons
    cmdListen.Enabled = True
    cmdClose.Enabled = False
End Sub
					

The last step is to add a single line of code to let the user know when a Winsock error occurs. Add the code in Listing 4.30 to the Error event of the Winsock control.

Code Listing 4.30. The wsSocket_Error Event
Private Sub wsSocket_Error(ByVal Number As Integer, _
        Description As String, ByVal Scode As Long, _
        ByVal Source As String, ByVal HelpFile As String, _
        ByVal HelpContext As Long, CancelDisplay As Boolean)
    '-- Inform the user of the error
    MsgBox "WinSock Error: " + Str(Number) + " - " + Description
End Sub
					

At this point, your application is complete. However, you cannot just run it from the Visual Basic development environment. You need to be able to run two copies of this application. This means that you need to build an executable version before you can test all the Winsock functionality. After you build an executable, you can run the executable version and also run a debug version within the Visual Basic IDE. You can alternate which one is running in Server mode and which is in Client mode so that you can step through all the Winsock code in the debugger.

When you have a compiled executable, you can run two copies of this application, either on the same computer, as shown in Figure 4.4, or on two separate computers that are connected over a network. Remember that the copy running in Server mode needs to start listening for connection requests before the client tries to connect to it.

Figure 4.4. Exchanging messages between two instances of the application.


If you are running both copies of the application on the same computer, you can use the computer name loopback as the server name for the client to connect to. Otherwise, you need to specify the network name of the computer on which the server is running. For the port to use, I recommend using port numbers around 4000. There is a gap of port numbers that aren't com monly used by standard applications at this point in time.

Tip

In order to run the example application, you'll need to have TCP/IP running on your computer. If you are not connected to a network and do not have a network card installed in your computer, you'll have to be connected to an ISP through a dial-up connection while running the example. The steps you follow to run this application are as follows:

  • Start two copies of the application (you need to have compiled it to an EXE to do this).

  • With the copy that will act as the Server, select the Server radio button, fill in a port to use (I suggest 4000), and click the Listen button.

  • With the copy that will act as the Client, select the Client radio button, fill in the name of the computer on which the Server copy is running (if the two copies are running on the same computer, use the name "loopback"), and then fill in the port number that you used on the Server copy. Click the Connect button to connect the two applications.

  • After the two applications are connected, they should perform the key exchange automatically. If they do not connect, you might want to close both applications and try again, using a different port number.

  • Type a message into the "Message" text box on either copy and click the Send Message button.

  • Do the same on the other copy.

  • After you are finished sending messages, click the Close button on either copy, and then click the Exit button on both copies.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset