Forum Discussion

William_Them_99's avatar
William_Them_99
Icon for Nimbostratus rankNimbostratus
Jul 20, 2005

Client Certificates at the Backend?

We have successfully configured the BIGIP device to require client certificates - it accepts the certs and passes the traffic through. Now, we need to be able to read and manipulate the client cert at the backend IIS web server via ASP/ASP.NET code.

 

 

I have configured IIS to require client certificates. If I visit the web server directly, it asks for the client cert, I choose the appropriate item, and the page loads and I am able to output (via ASP) the issuer and other attributes.

 

 

If I then go through the BIGIP to the backend web server, the BIGIP asks for the client cert, accepts it, and then I get a "Page cannot be displayed" error. If I then configure IIS to ignore client certificates, the error goes away and the page loads (albeit without displaying the issuer data on the page).

 

 

My guess is that the BIGIP is not pushing the client cert through to the backend. Is it possible to configure it to do so via iRules or some other method?

 

 

Thanks.
  • drteeth_127330's avatar
    drteeth_127330
    Historic F5 Account
    Please tell us something about your configuration. Since we are discussing client certificates, I assume that BIG-IP is performing SSL termination. Is this for offload purposes or is BIG-IP re-encrypting the traffic to the back-end servers? With SSL offload, the back-end traffic is unencrypted, so there really isn't a good way to offer a client cert to the back end. However, BIG-IP can be configured to perform the authorization itself. Additionally, the complete client certificate (or selected fields from it) can be included as custom HTTP headers. With re-encryption (also called SSL to server), the story changes somewhat. BIG-IP can offer a client certificate to the server. You can even choose which certificate to offer, but there is no good way to "push" the client's certificate through.
  • Oh ok, my mistake - so if we can put the whole cert in the header that could do the trick - do you have an example of that?

     

     

    Also, to answer the config question - I have a client SSL profile and a server SSL profile set up and working...
  • rapmaster_c_127's avatar
    rapmaster_c_127
    Historic F5 Account
    Try this on a virtual server with an attached SSL and HTTP profile:

    
    rule foo {
       when HTTP_REQUEST {
            if {[SSL::cert count] > 0} {
                HTTP::header replace SSLClientCert [b64encode [SSL::cert 0]]
            }
        }
    }

    This BASE64-encodes the client certificate, if any, into an HTTP header called "SSLClientCert", replacing it if the client already has a header by that name. You'd need to BASE64-decode it on the back-end, and then you'd have the original certificate again.

    Let us know if this works for you.
  • If there aren't builtin functions, I found this via Google.

    http://www.freevbcode.com/ShowCode.asp?ID=5248 (Click here)

    ' Functions to provide encoding/decoding of strings with Base64.
    ' 
    ' Encoding: myEncodedString = base64_encode( inputString )
    ' Decoding: myDecodedString = base64_decode( encodedInputString )
    '
    ' Programmed by Markus Hartsmar for ShameDesigns in 2002. 
    ' Email me at: mark@shamedesigns.com
    ' Visit our website at: http://www.shamedesigns.com/
    '
    Dim Base64Chars
    Base64Chars =   "ABCDEFGHIJKLMNOPQRSTUVWXYZ" & _
          "abcdefghijklmnopqrstuvwxyz" & _
          "0123456789" & _
          "+/"
    ' Functions for encoding string to Base64
    Public Function base64_encode( byVal strIn )
       Dim c1, c2, c3, w1, w2, w3, w4, n, strOut
       For n = 1 To Len( strIn ) Step 3
          c1 = Asc( Mid( strIn, n, 1 ) )
          c2 = Asc( Mid( strIn, n + 1, 1 ) + Chr(0) )
          c3 = Asc( Mid( strIn, n + 2, 1 ) + Chr(0) )
          w1 = Int( c1 / 4 ) : w2 = ( c1 And 3 ) * 16 + Int( c2 / 16 )
          If Len( strIn ) >= n + 1 Then 
             w3 = ( c2 And 15 ) * 4 + Int( c3 / 64 ) 
          Else 
             w3 = -1
          End If
          If Len( strIn ) >= n + 2 Then 
             w4 = c3 And 63 
          Else 
             w4 = -1
          End If
          strOut = strOut + mimeencode( w1 ) + mimeencode( w2 ) + _
                  mimeencode( w3 ) + mimeencode( w4 )
       Next
       base64_encode = strOut
    End Function
    Private Function mimeencode( byVal intIn )
       If intIn >= 0 Then 
          mimeencode = Mid( Base64Chars, intIn + 1, 1 ) 
       Else 
          mimeencode = ""
       End If
    End Function   
    ' Function to decode string from Base64
    Public Function base64_decode( byVal strIn )
       Dim w1, w2, w3, w4, n, strOut
       For n = 1 To Len( strIn ) Step 4
          w1 = mimedecode( Mid( strIn, n, 1 ) )
          w2 = mimedecode( Mid( strIn, n + 1, 1 ) )
          w3 = mimedecode( Mid( strIn, n + 2, 1 ) )
          w4 = mimedecode( Mid( strIn, n + 3, 1 ) )
          If w2 >= 0 Then _
             strOut = strOut + _
                Chr( ( ( w1 * 4 + Int( w2 / 16 ) ) And 255 ) )
          If w3 >= 0 Then _
             strOut = strOut + _
                Chr( ( ( w2 * 16 + Int( w3 / 4 ) ) And 255 ) )
          If w4 >= 0 Then _
             strOut = strOut + _
                Chr( ( ( w3 * 64 + w4 ) And 255 ) )
       Next
       base64_decode = strOut
    End Function
    Private Function mimedecode( byVal strIn )
       If Len( strIn ) = 0 Then 
          mimedecode = -1 : Exit Function
       Else
          mimedecode = InStr( Base64Chars, strIn ) - 1
       End If
    End Function

    *Note, I haven't verified this works...

    -Joe
  • This was a very helpful thread. My case is to do the same thing in java. I'm going to use the sun.misc.BASE64decoder to get the certificate. I do need to case the result to a X509Certificate[] object. The above just doesn't work. I get is as a byte[] but not a X509 object. I was thinking maybe X509::whole would work. How do I use that command. (ok, it's a newbie question).

     

     

    HTTP::header replace SSLClientCert [b64encode [X509::whole $the_cert]]

     

    Is this how it works? This creates an object in PEM format. My certs usually will have three levels. Would the PEM format load into the X509 java object. Maybe a sample java program would help me understand.
  • I don't have an example in Java, as I wrote our code in ASP.NET, but I can show you what we did.

     

     

    First, I used the iRule code mentioned in this post to put the cert in the header. Then I grabbed that value from the header with the code below:

     

     

     
      
     If Trim(Request.ServerVariables("HTTP_SSLCLIENTCERT")) <> "" Then 
      
                 CERTFROMHEADER.Value = Trim(Request.ServerVariables("HTTP_SSLCLIENTCERT")) 
      
     ... 
      
     End If 
      
     

     

     

    (Note that .NET prepends the "HTTP_" to all header variables)

     

     

    As discussed in this post, the value in my CERTFROMHEADER variable is a base64 encoded string - otherwise known as PEM format for the certificate. The problem is that .NET only allows you to manipulate the cert in the DER format (at least with the version of the framework we are running). So, I had to use the Microsoft utility CERTUTIL.EXE to convert the PEM cert to a DER cert and then open it as a .NET object as shown below:

     

     

     
      
     'create a file name for the temporary storage of the certificate to disk and its converted form 
       temp_file_name = "cert_PEM_" & Replace(Replace(DateTime.Now.TimeOfDay.ToString.Replace(".", "_"), ":", ""), " ", "") & ".cer" 
      
      
       temp_output_file_name = "cert_DER_" & Replace(Replace(DateTime.Now.TimeOfDay.ToString.Replace(".", "_"), ":", ""), " ", "") & ".cer" 
      
      
       file_save_result = SaveTextToFile(Trim(Request.Form.Item("CERTFROMHEADER")), temp_file_name) 
      
      
       'create a new process that runs the Cert utility to convert the cert to DER format 
       CertConversion = System.Diagnostics.Process.Start(certConversionBatchPath, temp_file_name & " " & temp_output_file_name) 
      
      
       'wait until the process completes before continuing 
       CertConversion.WaitForExit(1000) 
      
      
       'close the process 
                             CertConversion.Close() 
                             CertConversion.Dispose() 
                             CertConversion = Nothing 
      
      
       'using the created .cer file, load it as a certificate object 
       cert = X509Certificates.X509Certificate.CreateFromCertFile(temp_output_file_name) 
      
      
       'delete the temporary .cer file and its converted form 
                             File.Delete(temp_file_name) 
                             File.Delete(temp_output_file_name) 
      
     

     

     

    ------------------------------------------

     

     

     

    Even in Java, you may be able to execute the Microsoft utility to get the proper certificate format. CERTUTIL.EXE is part of Windows 2003, but you can also just download the Windows Server 2003 Administration Tools Pack here:

     

     

    http://www.microsoft.com/downloads/details.aspx?FamilyID=c16ae515-c8f4-47ef-a1e4-a8dcbacff8e3&DisplayLang=en

     

     

    You can do many things with CERTUTIL. To convert from PEM to DER, as you can see in the code, all I did was run the executable with an input file (in PEM format), and an output file name, and that worked. For other possible uses, see:

     

     

    http://technet2.microsoft.com/windowsserver/en/library/a3d5dbb9-1bf6-42da-a13b-2b220b11b6fe1033.mspx?mfr=true

     

     

    I hope this helps.

     

     

    -Bill
  • Here is the irule that I have working to get me the certificate information. It does pass it to Oracle in PEM format.

     

    I will post my Java solution to dealing with this next

     

     

    when CLIENTSSL_CLIENTCERT {

     

    if { [SSL::verify_result] } {

     

    log LOCAL0.warn "Client cert didn't verify, openssl code=[SSL::verify_result]"

     

    reject

     

    }

     

    }

     

     

    when HTTP_REQUEST {

     

    if { [HTTP::header exists SSL_Client_Cert] } {

     

    log LOCAL0.warn "removed inbound cert header - possible attack"

     

    reject

     

    }

     

     

    if { [SSL::cert count] != 0 } {

     

    set subject { }

     

    lappend subject [X509::subject [SSL::cert 0]]

     

    HTTP::header replace SSL_Client_Cert [X509::whole [SSL::cert 0]]

     

    HTTP::header replace SSL_Client_Cert_Chain_1 [X509::whole [SSL::cert 1]]

     

    }

     

    }

     

  • tamins_90207's avatar
    tamins_90207
    Historic F5 Account
    Greetings,

     

    I need a rule to extract cert SN, issuer, and expiration date. when I enable this irule.

     

    it's not only that nothing shows up in the header, but also I get page cannot be displayed after the client cert is presented.

     

     

     

    when CLIENTSSL_CLIENTCERT {

     

    set cert [SSL::cert 0]

     

    set sn [X509::serial_number $cert]

     

    set issuer [X509::issuer $cert]

     

    set not_valid_before [X509::not_valid_before $cert]

     

    }

     

    when HTTP_REQUEST {

     

    if { [matchclass [HTTP::uri] equals $::certURIs] } {

     

    if { [SSL::cert count] < 1 } {

     

    SSL::authenticate once

     

    SSL::authenticate depth 9

     

    SSL::cert mode request

     

    SSL::renegotiate

     

    } else {

     

    HTTP::header insert ClientSSL_Serial_F5 $sn

     

    HTTP::header insert ClientSSL_Issuer_F5 $issuer

     

    HTTP::header insert ClientSSL_not_valid_before_F5 $not_valid_before

     

    }

     

    }

     

    }

     

     

    I have tried variations of the rule above, but with no luck.

     

    any pointers would be greatly appreciated.

     

     

    TIA

     

     

    tsun