Thursday, May 01, 2014

Communication with a smart card from PowerBuilder

We're going to look at using the Smart Card SDK provided in more recent versions of Windows (XP and later).  Earlier versions of Windows had an ActiveX installed called CAPICOM which could be accessed from PowerBuilder through OLE Automation, but that control was removed as of Windows Vista because of security issues.

We're going to look at a number of operations:














Communicating with the card


The first step is establish a context for the API calls.  To do that, we need to declare the following local external function for SCardEstablishContext:

  1. Function ulong SCardEstablishContext  ( &  
  2.   Long dwScope, &  
  3.   long pvReserved1, &  
  4.   long pvReserved2, &  
  5.   REF ulong phContext &  
  6.   ) Library "winscard.dll"  
And call it as follows:

  1. ulong    rc  
  2. rc = scardestablishcontext( SCARD_SCOPE_USER, 0, 0, context )  
  3. IF rc = SCARD_S_SUCCESS THEN  
  4.     Return SUCCESS  
  5. ELSE  
  6.     Return FAILURE  
  7. END IF  
Where the following are defined as constants.

  1. CONSTANT LONG SCARD_S_SUCCESS            = 0  
  2. CONSTANT LONG SCARD_SCOPE_USER          = 0  
Context (the last argument) should be defined as an instance variable because we will need to release it when we are done making API calls.  We will also be passing a reference to it in many of the API calls.

  1. ulong        context  
The second step is get a reference to the smart card reader(s) that the user's machine is equipped with.  We do that by declaring the following local external function for the SCardListReaders method in the SDK:

  1. Function ulong SCardListReaders  ( &  
  2.   ulong hContext, &  
  3.   Long mszGroups, &  
  4.   REF Blob mszReaders, &  
  5.   REF Long pcchReaders &  
  6.   ) Library "winscard.dll" Alias For "SCardListReadersW"  
The API defines the second argument as a string.  However, we want to pass a null, and the way we do that with PowerBuilder is by declaring the argument as a long and then passing a zero.  That causes the method to return all readers, rather than just a subset.

Once we've declared that we can use the following PowerScript to get an array that contains the name of all smart card readers on the system.  The SDK returns the list as a null terminated array, so we parse the blob we get back and use the String function to pull the names out and put them in an array that PowerBuilder is more comfortable with.

  1. ulong                    rc  
  2. long                    ll_bufflen = 32000  
  3. long                    ll_readerlen  
  4. string                    ls_reader = Space ( ll_bufflen )  
  5. blob                    buffer  
  6. buffer = Blob ( ls_reader )  // Preallocate space in the buffer or the call will fail  
  7. rc = SCardListReaders ( context, 0, buffer, ll_bufflen )  
  8. IF ll_bufflen > 0 THEN  
  9.     // BlobMid is messed up.  Len reports Unicode chars, BlobMid acts like ANSI chars  
  10.     // so we have to double the values we pass to it to get full Unicode characters  
  11.     //Truncate the buffer to what was actually returned  
  12.     buffer = BlobMid ( buffer, 1, ( ll_bufflen - 1 ) * 2 )  
  13.     //Read off the first value (string stops at the null terminator)  
  14.     ls_reader = String ( buffer )  
  15.     //Add it to the array  
  16.     readers[1] = ls_reader  
  17.     //See if we have any data left  
  18.     ll_readerlen = ( Len ( ls_reader ) * 2 ) + 3  
  19.     buffer = BlobMid ( buffer, ll_readerlen )  
  20.     ll_bufflen = Len ( buffer )  
  21.     //Loop through for the remaining data  
  22.     DO WHILE ll_bufflen > 0  
  23.         ls_reader = String ( buffer )  
  24.         readers[UpperBound(readers)+1] = ls_reader  
  25.         ll_readerlen = ( Len ( ls_reader ) * 2 ) + 3  
  26.         buffer = BlobMid ( buffer, ll_readerlen )  
  27.         ll_bufflen = Len ( buffer )  
  28.     LOOP  
  29. END IF  
If there are no readers, there isn't much further to go.  If there is only one, then you can proceed immediately to check if there is a smart card in it.  If there is more than one reader, you may need to check them all to see which have cards in them, and if more than one do then prompt the user to select the card they want to work with.

There is a function in the SDK that can do some of this work for you (SCardUIDlgSelectCard).  It determines what cards are available and prompts the user to select the one they want to work with.  It does require working with an OPENCARDNAME_EX structure though, which is a bit tricky from PowerBuilder.  For a number of reasons, we found that method didn't serve our needs.  As a result, this sample does all of the work without using that method.

The third step is to attempt to open a connection to any card(s) in the card reader(s) we've found.  We do that by first declaring the following local external function for the SCardConnect method in the SDK.

  1. Function ulong SCardConnect  ( &  
  2.     Long hContext, &  
  3.     String szReader, &  
  4.     Long dwShareMode, &  
  5.     Long dwPreferredProtocols, &  
  6.     REF ulong phCard, &  
  7.     REF ulong pdwActiveProtocol &  
  8.     ) Library "winscard.dll" Alias For "SCardConnectW"  
And then call it in PowerScript as follows:

  1. ulong    rc  
  2. rc = SCardConnect ( context, &  
  3.                              reader, &  
  4.                              SCARD_SHARE_SHARED, &  
  5.                              SCARD_PROTOCOL_Tx, &  
  6.                              card, &  
  7.                              protocol )  
Where "reader" is the name of the smart card reader we obtained from the previous step.  The next two arguments are defined constants as follow (along with some other values you might use instead):

  1. CONSTANT LONG SCARD_SHARE_EXCLUSIVE    = 1  
  2. CONSTANT LONG SCARD_SHARE_SHARED        = 2  
  3. CONSTANT LONG SCARD_SHARE_DIRECT        = 3  
  4. CONSTANT LONG SCARD_PROTOCOL_T0         = 1  
  5. CONSTANT LONG SCARD_PROTOCOL_T1         = 2  
  6. CONSTANT LONG SCARD_PROTOCOL_Tx         = 3  
Once again, you'll want to save "card" and "protocol" off as instance variables, because you'll need to use them later.

  1. protected ulong        card  
  2. protected ulong        protocol  
The fourth step (assuming we found a card) is to get the card status to ensure that it's ready for us to communicate with it.  We do that using the SCardStatus API function:

  1. Function ulong SCardStatus  ( &  
  2.     ulong hCard, &  
  3.     REF String szReaderName, &  
  4.     REF Long pcchReaderLen, &  
  5.     REF Long pdwState, &  
  6.     REF Long pdwProtocol, &  
  7.     REF byte pbAtr[], &  
  8.     REF Long pcbAtrLen &  
  9.     ) Library "winscard.dll" Alias For "SCardStatusW"  
Which we then call via the following PowerScript:

  1. integer    i  
  2. ulong    rc  
  3. long    pcchReaderLen = 32000  
  4. string    szReaderName = Space ( pcchReaderLen )  
  5. long    pdwState  
  6. long    pdwProtocol  
  7. byte     pbAtr[]  
  8. long    pcbAtrLen = 32  
  9. FOR i = 1 TO pcbAtrLen  
  10.     pbAtr[i] = Byte ( Space(1) ) // Prepad the buffer  
  11. NEXT  
  12. rc = SCardStatus( card, &  
  13.     szReaderName, &  
  14.     pcchReaderLen, &  
  15.     pdwState, &  
  16.     pdwProtocol, &  
  17.     pbAtr, &  
  18.     pcbAtrLen )  
  19. IF rc = SCARD_S_SUCCESS THEN  
  20.     CHOOSE CASE pdwState  
  21.         CASE SCARD_UNKNOWN  
  22.             status = 'Unknown'  
  23.         CASE SCARD_ABSENT  
  24.             status= 'Absent'  
  25.         CASE SCARD_PRESENT  
  26.             status= 'Present'  
  27.         CASE SCARD_SWALLOWED  
  28.             status= 'Swallowed'  
  29.         CASE SCARD_POWERED  
  30.             status= 'Powered'  
  31.         CASE SCARD_NEGOTIABLE  
  32.             status= 'Negotiable'  
  33.         CASE SCARD_SPECIFIC  
  34.             status= 'Specific'  
  35.         CASE ELSE  
  36.             status= 'Not Known'  
  37.     END CHOOSE  
  38.     atrbytes = pbAtr  
  39.     atr = ""  
  40.     FOR i = 1 TO pcbAtrLen  
  41.         atr += of_bytetohex ( pbAtr[i] )  
  42.     NEXT  
  43. END IF  
Once again, you may want to save off the "status" and the "atr" (Answer to Reset) value.  For ease in using them from PowerBuilder, I've converted them to strings (the ATR being a hex encoded string).

The constants for the various possible state values are:

  1. CONSTANT LONG SCARD_UNKNOWN               = 0  
  2. CONSTANT LONG SCARD_ABSENT                = 1  
  3. CONSTANT LONG SCARD_PRESENT               = 2  
  4. CONSTANT LONG SCARD_SWALLOWED             = 3  
  5. CONSTANT LONG SCARD_POWERED               = 4  
  6. CONSTANT LONG SCARD_NEGOTIABLE            = 5  
  7. CONSTANT LONG SCARD_SPECIFIC              = 6  
And the of_bytetohex function was borrowed from PFC.

  1. string    ls_hex=''  
  2. char    lch_hex[0 to 15] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', &  
  3.                             'A', 'B', 'C', 'D', 'E', 'F'}  
  4. Do  
  5.     ls_hex = lch_hex[mod (abyte, 16)] + ls_hex  
  6.     abyte /= 16  
  7. Loop Until abyte = 0  
  8. IF Len ( ls_hex ) = 1 THEN  
  9.     ls_hex = '0' + ls_hex  
  10. END IF  
  11. Return ls_hex  
Now that we're connected to the card, the fifth step is to connect to the specific applet on the smart card we want to work with.  For this, you'll need to have information from the card manufacturer as to the applet IDs you need to communicate with and the specific APDU command syntax they require.  If you are working with US DoD Common Access Cards, a great deal of that information can be obtained from Common Access Card (CAC): Home, particularlyCommon Access Card (CAC) Security.  The "Test Materials" section contains links for instructions on obtaining test cards and the form to do so.  Also of particular interest on that page are DoD Implementation Guide for CAC Next Generation (NG) and Technical Bulletin: CAC Data Model Change in 144K Dual Interface 

We'll need to declare the SCardTransmit API call to send commands to the applet:

  1. Function ulong SCardTransmit( &  
  2.       ulong hCard, &  
  3.       Long pioSendPci, &  
  4.      byte pbSendBuffer[], &  
  5.       Long cbSendLength, &  
  6.       Long pioRecvPci, &  
  7.       REF byte pbRecvBuffer[], &  
  8.       REF long pcbRecvLength &  
  9.     ) Library "winscard.dll"  
The API documentation indicates that the second argument is a SCARD_IO_REQUEST structure, but we're going to use a pointer to that structure that we obtain from the SDK.  The API documentation indicates that the fifth argument is a similar structure, but we're going to declare it as a long and pass 0 to indicate that we're passing null for that value.

We create a wrapper function because we're going to be sending a number of commands to the card.  The function takes a byte array as an argument and returns a string with status info.

  1. integer    i  
  2. ulong        rc  
  3. byte        sendBuffer[]  
  4. long        pioSendPci  
  5. byte         pbRecvBuffer[]  
  6. long         pcbRecvLength  
  7. long        cbSendLength  
  8. byte        getdata[5]  
  9. string        respSW1  
  10. string        respSW2  
  11. cbSendLength = UpperBound ( apdu )  
  12. FOR i = 1 TO cbSendLength  
  13.     sendBuffer[i] = of_hextobyte ( apdu[i] )  
  14. NEXT  
  15. pcbRecvLength = 2  
  16. FOR i = 1 TO pcbRecvLength  
  17.     pbRecvBuffer[i] = 0 ;  
  18. NEXT  
  19. pioSendPci = of_getpci()  
  20. rc = SCardTransmit ( card, &  
  21.       pioSendPci, &  
  22.     sendBuffer, &  
  23.       cbSendLength, &  
  24.       0, &  
  25.       pbRecvBuffer, &  
  26.      pcbRecvLength )  
  27. IF rc <> scard_s_success THEN return ""  
  28. respSW1 = of_bytetohex ( pbRecvBuffer[1] )  
  29. respSW2 = of_bytetohex ( pbRecvBuffer[2] )  
  30. //This means there is more data to come  
  31. IF respSW1 = "61" THEN  
  32.     getdata[1] = of_hextobyte ( "00" )  
  33.     getdata[2] = of_hextobyte ( "C0" )  
  34.     getdata[3] = of_hextobyte ( "00" )  
  35.     getdata[4] = of_hextobyte ( "00" )  
  36.     getdata[5] = pbRecvBuffer[2]  
  37.     cbSendLength = 5  
  38.     pcbRecvLength = pbRecvBuffer[2] + 2 // We need two extra bytes for the status bits  
  39.     FOR i = 1 TO pcbRecvLength  
  40.         pbRecvBuffer[i] = 0 ;  
  41.     NEXT  
  42.     rc = SCardTransmit ( card, &  
  43.         pioSendPci, &  
  44.         getdata, &  
  45.         cbSendLength, &  
  46.         0, &  
  47.         pbRecvBuffer, &  
  48.         pcbRecvLength )  
  49.     IF rc <> SCARD_S_SUCCESS THEN Return ""  
  50.     respSW1 = of_bytetohex ( pbRecvBuffer[pcbRecvLength - 1] )  
  51.     respSW2 = of_bytetohex ( pbRecvBuffer[pcbRecvLength] )  
  52. END IF  
  53. Return respSW1 + respSW2  
The script uses another method borrowed from PFC (of_hextobyte)  to convert a hex value to a byte:

  1. char        lch_char[]  
  2. integer    li_byte  
  3. int li_dec[48 to 70], li_i, li_len  
  4. //Get the decimal code for hexadecimal value of '0' to 'F'  
  5. // Whose ASC Value are from 48 to 57 and 65 to 70  
  6. For li_i = 48 To 57  
  7.     li_dec[li_i] = li_i - 48  
  8. Next  
  9. For li_i = 65 To 70  
  10.     li_dec[li_i] = li_i - 55  
  11. Next  
  12. as_hex = upper(as_hex)  
  13. lch_char = as_hex  
  14. li_len = len (as_hex)  
  15. //Convert Hexadecimal data into decimal  
  16. For li_i = 1 to li_len  
  17.     //Make sure only 0's through f's are present  
  18.     Choose Case lch_char[li_i]  
  19.         Case '0' to '9', 'A' to 'F'  
  20.             li_byte = li_byte * 16 + li_dec[asc(lch_char[li_i])]  
  21.         Case Else  
  22.             Return li_byte  
  23.     End Choose  
  24. Next  
  25. Return li_byte  
The of_getpci method is the one that gets a pointer to a LPCSARD_IO_REQUEST structure from the winscard DLL:

  1. ulong dllhandle  
  2. ulong pci  
  3. dllhandle = LoadLibrary ( "WinSCard.dll" )  
  4. pci = GetProcAddress ( dllhandle, "g_rgSCardT0Pci" )  
  5. FreeLibrary(dllhandle)  
  6. return pci  
That method in turn relies on external function declarations for three Windows API functions:

  1. Function ulong LoadLibrary ( &  
  2.     string fileName&  
  3.     ) Library "kernel32.dll" Alias For "LoadLibraryW"  
  4. SubRoutine FreeLibrary ( &  
  5.     ulong dllhandle &  
  6.     ) Library "kernel32.dll"  
  7. Function ulong GetProcAddress ( &  
  8.     ulong dllhandle, &  
  9.     string procName &  
  10.     ) Library "kernel32.dll" Alias For "GetProcAddress;Ansi"  
The return value from the SCardTransmit is "9000" if it works.  If the first two characters are "61", it means that the call returns more data than could be returned in the original request.  If that is the case, a second call is made to the function to retrieve the rest of the data.

Ok, now we're ready to select the applet.  This is the card specific process, so the following example will only work for US DoD CAC cards.  You will need to consult information from the manufacturer of the card for specific details on how to deal with it.

There are two versions of the US DoD CAC card, one in which you need to access an applet ID of 790101 (the first version).  If that fails, we attempt to connect to a container applet ID of 790100 and, if that works, an applet within it through it's object ID of 0101.

  1. string    apdu[]  
  2. string    respSW  
  3. string    emptyarray[]  
  4. apdu[1] = "00"  
  5. apdu[2] = "A4" // Select  
  6. apdu[3] = "04" // By id  
  7. apdu[4] = "00" // first record  
  8. apdu[5] = "07" // Lenth of data  
  9. apdu[6] = "A0" // Applet ID to bit 11  
  10. apdu[7] = "00"  
  11. apdu[8] = "00"  
  12. apdu[9] = "00"  
  13. apdu[10] = "79"  
  14. apdu[11] = "01"  
  15. apdu[12] = "01"  
  16. respSW = of_sendapdu ( apdu )  
  17. // If we got OK, then it's an old card and we're already good  
  18. IF respSW =  SWRESPOK THEN  
  19.   version = '1'  
  20. ELSE  
  21.     //Otherwise, try using the new card method  
  22.     //Select the master applet  
  23.     apdu[12] = "00"  
  24.     respSW = of_sendapdu ( apdu )  
  25.     IF respSW <> SWRESPOK THEN  
  26.         //We don't know what it is  
  27.         Return FAILURE  
  28.     END IF  
  29.     //Reset the array  
  30.     apdu = emptyarray  
  31.     apdu[1] = "00" // CLA  
  32.     apdu[2] = "A4" // INS - select object  
  33.     apdu[3] = "02" // P1  
  34.     apdu[4] = "00" // P2 -  
  35.     apdu[5] = "02" // Lc - length of data  
  36.     apdu[6] = "01" // file id  
  37.     apdu[7] = "01"  
  38.     respSW = of_sendapdu ( apdu )  
  39.     IF respSW = SWRESPOK THEN  
  40.         version = '2'  
  41.     ELSE  
  42.         //We couldn't select the applet  
  43.         version = 'Unknown'  
  44.         Return FAILURE  
  45.     END IF  
  46. END IF  
  47. Return SUCCESS  
Before we go into specific operations, let's see what we need to do to clean up after ourselves when we're done.  So, for our sixth step we need to disconnect from the card.  To do that, we declare a local external function for theSCardDisconnect method in the SDK:

  1. Function ulong SCardDisconnect ( &  
  2.     ulong hCard, &  
  3.     long dwDisposition &  
  4.     ) Library "winscard.dll"  
And call it with the following PowerScript:

  1. ulong    rc  
  2. rc = scarddisconnect( card, SCARD_LEAVE_CARD )  
  3. SetNull ( card )  
  4. IF rc = SCARD_S_SUCCESS THEN  
  5.     Return SUCCESS  
  6. ELSE  
  7.     Return FAILURE  
  8. END IF  
Where SCARD_LEAVE_CARD (and other values you might need to use) are defined as:

  1. CONSTANT LONG SCARD_LEAVE_CARD            = 0  
  2. CONSTANT LONG SCARD_RESET_CARD            = 1  
  3. CONSTANT LONG SCARD_UNPOWER_CARD      = 2  
Finally, in our seventh step, we need to release the context that we established in the first step.  We need theSCardReleaseContext SDK method for that:

  1. Function ulong SCardReleaseContext( &  
  2.     ulong hContext &  
  3.     ) Library "winscard.dll"  
And then use the following PowerScript to call it:

  1. ulong rc  
  2. rc = scardreleasecontext( context )  
  3. IF rc = SCARD_S_SUCCESS THEN  
  4.     Return SUCCESS  
  5. ELSE  
  6.     Return FAILURE  
  7. END IF  

Validating the user's PIN


Now that we know how to connect to the card, we'll look at some specific operations that we might want to perform using it.  One is to have the user validate their PIN so we know that the user is actually the holder of the card.  To do that, we need to send a specific APDU command to the card while we're connected to it.  For US DoD CAC cards, the value is 00 20 00 followed by the length of the PIN buffer ( "08" ) and then the hex encoded PIN right padded to the length of the PIN buffer with FF values.

In the following sample, w_pin is a window that is presented to the user into which they enter their PIN.  The return code from the APDU call, if the PIN is invalid, indicates the number of additional attempts the user is allowed to make before the card is automatically locked and the user will need to go to a RAPIDS site to have the card unlocked.

  1. int        i  
  2. int        chances  
  3. int        pinlen  
  4. string apdu[]  
  5. string    ls_pin  
  6. string    respSW  
  7. Open ( w_pin )  
  8. ls_pin = message.StringParm  
  9. // User hit cancel  
  10. IF ls_pin = "" THEN Return NO_ACTION  
  11. pinlen = Len ( ls_pin )  
  12. apdu[1] = "00" // CLA  
  13. apdu[2] = "20"  // Verify PIN  
  14. apdu[3] = "00" // Not used  
  15. apdu[4] = "00" // Not used  
  16. apdu[5] = "08" // Indicate length of data, fixed at 8  
  17. // Add the PIN to the APDU  
  18. FOR i = 1 TO pinlen  
  19.     apdu[5 + i] = of_bytetohex ( Asc ( Mid ( ls_pin, i, 1 ) ) )  
  20. NEXT  
  21. // Pad out the rest of the APDU with 0xFF  
  22. FOR i = pinlen + 6 TO 13  
  23.     apdu[i] = "FF"  
  24. NEXT  
  25. respSW = of_sendapdu ( apdu )  
  26. CHOOSE CASE respSW  
  27.     CASE SWRESPOK  
  28.         // PIN Verified  
  29.         Return SUCCESS  
  30.     CASE PINLOCKED  
  31.         Return PIN_LOCKED  
  32.     CASE CACLOCKED  
  33.         Return CAC_LOCKED  
  34.     CASE INVALIDDATA  
  35.         Return INVALID_DATA  
  36.     CASE PINUNDEFINED  
  37.         Return PIN_UNDEFINED  
  38.     CASE ELSE  
  39.         IF Left ( respSW, 2 ) = PININVALID THEN  
  40.             chances = Integer ( Right ( respSW, 1 ) )  
  41.             Return -chances  
  42.         End If  
  43. END CHOOSE  
The possible status codes returned from the APDU call are defined as:

  1. CONSTANT STRING PININVALID         = "63"  
  2. CONSTANT STRING PINLOCKED        = "63C0"  
  3. CONSTANT STRING CACLOCKED       = "6983"  
  4. CONSTANT STRING INVALIDDATA      = "6984"  
  5. CONSTANT STRING PINUNDEFINED   = "6A88"  

Reading the certificate Subject Name


The other thing we may want to do, once we've verified that the user knows the CAC PIN, is determine who the CAC card says the user is.  To do that, we're going to read the certificate off the card and then determine what the CN value of the Subject Name is. The US DoD stores the user's name and EDI/PI number in the CN value in the following format:  CN=LastName.FirstName.MiddleName.EDI/PI

The first step to access the certificate is to establish a cryptography context using the Windows APICryptAcquireContext method:

  1. Function ulong CryptAcquireContext ( &  
  2.     REF ulong hProv, &  
  3.     ulong pszContainer, &  
  4.     string pProviderName, &  
  5.     long dwProvType, &  
  6.     long dwFlags &  
  7.     ) Library "advapi32.dll" Alias For "CryptAcquireContextW"  
And call it using this PowerScript:

  1. ulong    rc  
  2. rc = CryptAcquireContext ( &  
  3.     prov, &  
  4.     0, &  
  5.     providername, &  
  6.     PROV_RSA_FULL, &  
  7.     0 )  
"prov" is a pointer to the cryptography context that is passed by reference returned to us as a result of the call, defined as:

  1. ulong        prov  
We declared pszContainer as a ulong rather than a string (as in the SDK) because we're passing 0 indicating a null value.  "providername" is also passed by reference as is defined as follows.  However, we don't use the value.

  1. string        providername  
And PROV_RSA_FULL is defined as:

  1. CONSTANT LONG PROV_RSA_FULL                = 1  
The second step, once we have the context, we need to call CryptGetUserKey in the Windows API to get a handle to the certificate:

  1. Protected Function ulong CryptGetUserKey ( &  
  2.     ulong hProv, &  
  3.     long dwKeySpec, &  
  4.       REF ulong phUserKey &  
  5.     ) Library "advapi32.dll"  
And call it as follows:

  1. ulong        rc  
  2. rc = CryptGetUserKey ( &  
  3.   prov, &  
  4.   AT_KEYEXCHANGE, &  
  5.   userkey )  
Where AT_KEYEXCHANGE is defined as:

  1. LONG AT_KEYEXCHANGE   = 1  
And "userkey" is passed by referenced and returned to us by the method, defined as:

  1. ulong        userkey  
The third step is to call CryptGetKeyParam in the Windows API, declared as follows, to get the actual certificate:

  1. Function ulong CryptGetKeyParam ( &  
  2.     ulong hKey, &  
  3.     long dwParam, &  
  4.     REF byte pbData[], &  
  5.     REF long pdwDataLen, &  
  6.     long dwFlags &  
  7.     ) Library "advapi32.dll"  
And call it as follows:

  1. integer    i  
  2. ulong        rc  
  3. long        certlen  
  4. byte        temp[]  
  5. //Call it with 0 first to get the size  
  6. certlen = 0  
  7. rc =  CryptGetKeyParam ( &  
  8.         userkey, &  
  9.         KP_CERTIFICATE, &  
  10.         temp, &  
  11.         certlen, &  
  12.         0 )  
  13. //Now setup the buffer and call again for that size  
  14. FOR i = 1 TO certlen  
  15.     temp[i] = 0  
  16. NEXT  
  17. rc =  CryptGetKeyParam ( &  
  18.         userkey, &  
  19.         KP_CERTIFICATE, &  
  20.         temp, &  
  21.         certlen, &  
  22.         0 )  
  23. certbytes = temp  
Where KP_CERTIFICATE is defined as:

  1. CONSTANT LONG KP_CERTIFICATE                    = 26  
As indicated in the code comments, we call the function once with certlen set to 0 and the function returns the size of the certificate to use.  We then call it a second time with a byte buffer populated to that size to get the certificate data.

The fourth step once we have the certificate data is to convert it to a certificate.  To do that, we first have to establish a certificate context using CertCreateCertificateContext in the Windows API:

  1. Protected Function ulong CertCreateCertificateContext ( &  
  2.     long dwCertEncodingType, &  
  3.     byte pbCertEncoded[], &  
  4.     long cbCertEncoded &  
  5.     ) Library "crypt32.dll"  
And call it as follows:

  1. long    certlen  
  2. long    encoding = X509_ASN_ENCODING + PKCS_7_ASN_ENCODING  
  3. certlen = UpperBound ( certbytes )  
  4. certContextPointer = CertCreateCertificateContext ( &  
  5.     encoding, &  
  6.     certbytes, &  
  7.     certlen )  
Where X509_ASN_ENCODING and PKCS_7_ASN_ENCODING are defined as:

  1. CONSTANT LONG PKCS_7_ASN_ENCODING       = 65536 // 0x00010000  
  2. CONSTANT LONG X509_ASN_ENCODING            = 1  
And certContextPointer is defined as:

  1. ulong    certcontextpointer  
The fifth step is to get the Subject Name off the certificate using the CertGetNameString method in the Windows API:

  1. Function ulong CertGetNameString ( &  
  2.     ulong pCertContext, &  
  3.     long dwType, &  
  4.     long dwFlags, &  
  5.     long pvTypePara, &  
  6.     REF string pszNameString, &  
  7.     REF long cchNameString &  
  8.     ) Library "crypt32.dll" Alias For "CertGetNameStringW"  
And call it as follows:

  1. ulong        rc  
  2. long        subjectlen  
  3. string        ls_subject  
  4. subjectlen = 256  
  5. ls_subject = Space ( subjectlen )  
  6. rc = CertGetNameString ( &  
  7.     certContextPointer, &  
  8.     CERT_NAME_SIMPLE_DISPLAY_TYPE, &  
  9.     0, &  
  10.     0, &  
  11.     ls_subject, &  
  12.     subjectlen )  
Where CERT_NAME_SIMPLE_DISPLAY_TYPE is defined as:

  1. CONSTANT LONG CERT_NAME_SIMPLE_DISPLAY_TYPE = 4  
Now that we have the subject name, we need to release the certificate context.  We do that with theCertFreeCertificateContext Windows API function.

  1. Function ulong CertFreeCertificateContext (&  
  2.     ulong pCertContext &  
  3.     ) Library "crypt32.dll"  
Which we call as follows:

  1. ulong    rc  
  2. rc = CertFreeCertificateContext ( certcontextpointer )  
After which we need to release the cryptography context as well using the CryptReleaseContext Windows API function:

  1. Function ulong CryptReleaseContext ( &  
  2.     ulong hProv, &  
  3.     long dwFlags &  
  4.     ) Library "advapi32.dll"  
And call as follows:

  1. ulong rc  
  2. rc = CryptReleaseContext ( &  
  3.     prov, &  
  4.     0 )  

Reading other Certificate Data


Once we have a pointer to a CERT_CONTEXT structure, one member of which is a CERT_INFO structure, we can actually access the certificate.  The first thing we need to do is create a PowerBuilder structure we can copy the data into.

  1. global type cert_context from structure  
  2.   long dwcertencodingtype  
  3.   unsignedlong pbcertencoded  
  4.   long cbcertencoded  
  5.   unsignedlong pcertinfo  
  6.   unsignedlong hcertstore  
  7. end type  
Then declare the following which we can use to perform the copy:

  1. Protected Subroutine CopyCERT_CONTEXT ( &  
  2.   ref CERT_CONTEXT dest, &  
  3.   ulong source, &  
  4.   long buffsize &  
  5.   ) Library "kernel32" Alias For "RtlMoveMemory"  
And we call it as follows:

  1. cert_context lstr_cert_context  
  2. CONSTANT LONG CERT_CONTEXT_SIZE = 20  
  3. long buffersize =  
  4. CopyCERT_CONTEXT ( lstr_cert_context, certcontextpointer, CERT_CONTEXT_SIZE )  
Now we're going to declare a PowerBuilder structure to hold the certificate information:

  1. global type cert_info from structure  
  2.      long          dwversion  
  3.      crypt_integer_blob          serialnumber  
  4.      crypt_algorithm_identifier          signaturealgorithm  
  5.      cert_name_blob          issuer  
  6.      filetime          notbefore  
  7.      filetime          notafter  
  8.      cert_name_blob          subject  
  9.      cert_public_key_info          subjectpublickeyinfo  
  10.      crypt_bit_blob          issueruniqueid  
  11.      crypt_bit_blob          subjectuniqueid  
  12.      long          cextension  
  13.      unsignedlong          rgextension  
  14. end type  
The crypt_integer_blob structure referenced above is defined as:

  1. global type crypt_integer_blob from structure  
  2.      long          cbdata  
  3.      long          pbdata  
  4. end type  
The crypt_algorithm_identifier structure referenced above is defined as:

  1. global type crypt_algorithm_identifier from structure  
  2.      long          pszobjid  
  3.      crypt_objid_blob          parameters  
  4. end type  
The crypt_objid_blob structure referenced here is defined as:

  1. global type crypt_objid_blob from structure  
  2.      long          cbdata  
  3.      long          pbdata  
  4. end type  
The cert_name_blob structure referenced above is defined as:

  1. global type cert_name_blob from structure  
  2.      long          cbdata  
  3.      long          pbdata  
  4. end type  
The filetime structure referenced above is defined as:

  1. global type filetime from structure  
  2.      long          lowdatetime  
  3.      long          highdatetime  
  4. end type  
The cert_public_key_info structure referenced above is defined as:

  1. global type cert_public_key_info from structure  
  2.      crypt_algorithm_identifier          algorithm  
  3.      crypt_bit_blob          publickey  
  4. end type  
The crypt_algorithim_identifier structure is defined above.  The crypt_bit_blob structure is defined as:

  1. global type crypt_bit_blob from structure  
  2.      long          cbdata  
  3.      long          pbdata  
  4.      long          cUnusedBits  
  5. end type  
Then declare the following in order to copy the information from the cert_into portion of the cert_context into the PowerBuilder structure:

  1. Protected Subroutine CopyCERT_INFO ( &  
  2.   ref CERT_INFO dest, &  
  3.   ulong source, &  
  4.   long buffsize &  
  5.   ) Library "kernel32" Alias For "RtlMoveMemory"  
And then call it as follows:

  1. cert_info               lstr_cert_info  
  2. CONSTANT LONG CERT_INFO_SIZE                    = 112  
  3. long buffsize = CERT_INFO_SIZE  
  4. CopyCERT_INFO ( lstr_cert_info, lstr_cert_context.pcertinfo, buffsize )  
Now let's access some of the certification information.  We're going to need the following function declared so we can convert the start and end dates of the certificate's valid lifetime.

  1. Protected Function ulong FileTimeToSystemTime ( &  
  2.   filetime lpFileTime, &  
  3.   REF systemtime lpSystemTime &  
  4.   ) Library "kernel32.dll"  
We're going to need to define a PowerBuilder structure to pass for the systemtime.

  1. global type systemtime from structure  
  2.      uint          wYear  
  3.      uint          wMonth  
  4.      uint          wDayOfWeek  
  5.      uint          wDay  
  6.      uint          wHour  
  7.      uint          wMinute  
  8.      uint          wSecond  
  9.      uint          wMilliseconds  
  10. end type  
And then call it as follows:

  1. systemtime          lstr_notbefore  
  2. systemtime          lstr_notafter  
  3. ulong                    rc  
  4. rc = FileTimeToSystemTime( lstr_cert_info.notbefore, lstr_notbefore )  
  5. rc = FileTimeToSystemTime( lstr_cert_info.notafter, lstr_notafter )  
The following will convert the systemtime structures to a PowerBuilder datetime variables

  1. datetime notbefore  
  2. datetime notafter  
  3. notbefore = DateTime ( Date ( lstr_notbefore.wyear, lstr_notbefore.wmonth, lstr_notbefore.wday ), &  
  4.   Time ( lstr_notbefore.whour, lstr_notbefore.wminute, lstr_notbefore.wsecond, lstr_notbefore.wMilliseconds ) )  
  5. notafter = DateTime ( Date ( lstr_notafter.wyear, lstr_notafter.wmonth, lstr_notafter.wday ), &  
  6.   Time ( lstr_notafter.whour, lstr_notafter.wminute, lstr_notafter.wsecond, lstr_notafter.wMilliseconds ) )  
Finally we're going to grab the public certificate both as a byte array and as a hex string.  We're going to need one more function declaration:

  1. Protected Subroutine CopyCertEncoded ( &  
  2.   ref byte dest[], &  
  3.   ulong source, &  
  4.   long buffsize &  
  5.   ) Library "kernel32" Alias For "RtlMoveMemory"  
And then call it as follows:

  1. byte                    encodedcert[]  
  2. string                    ls_cert  
  3. buffsize = lstr_cert_info.subjectpublickeyinfo.publickey.cbdata  
  4. FOR i = 1 TO buffsize  
  5.      encodedcert[i] = 0  
  6. NEXT  
  7. CopyCertEncoded ( encodedcert, lstr_cert_info.subjectpublickeyinfo.publickey.pbdata, buffsize )  
  8. FOR i = 1 TO buffsize  
  9.      ls_cert += of_bytetohex ( encodedcert[i] )  
  10. NEXT  


No comments: