Request Client Certificate And Pass To Application

Problem this snippet solves:

We are using BigIP to dynamically request a client certificate. This example differs from the others available in that it actually passes the x509 certificate to the server for processing using a custom http header.

The sequence of event listeners required to accomplish this feat is: HTTP_REQUEST, which invokes CLIENTSSL_HANDSHAKE, which is followed by HTTP_REQUEST_SEND

The reason is that CLIENTSSL_HANDSHAKE occurs after HTTP_REQUEST event is processed entirely, but HTTP_REQUEST_SEND occurs after it. The certificate appears in PEM encoding and is slightly mangled; you need to emit newlines to get back into proper PEM format:

-----BEGIN CERTIFICATE------
Mabcdefghj...
-----END CERTIFICATE-----

This certificate can be converted to DER encoding by jettisoning the BEGIN and END markers and doing base64 decode on the string.

Code :

# Initialize the variables on new client tcp session.
when CLIENT_ACCEPTED {
    set collecting 0
    set renegtried 0
}

# Runs for each new http request
when HTTP_REQUEST {
    # /_hst name and ?_hst=1 parameter triggers client cert renegotiation
    if { $renegtried == 0
         and [SSL::cert count] == 0
         and    ([HTTP::uri] matches_regex {^[^?]*/_hst(\?|/|$)} 
              or [HTTP::uri] matches_regex {[?&]_hst=1(&|$)}) } {

# Collecting means buffering the request. The collection goes on
# until SSL::renegotiate occurs, which happens after the HTTP
# request has been received. The maximum data buffered by collect
# is 1-4 MB.
HTTP::collect
        set collecting 1
        SSL::cert mode request
        SSL::renegotiate
    }
}

# After a handshake, we log that we have tried it. This is to prevent
# constant attempts to renegotiate the SSL session. I'm not sure of this
# feature; this may in fact be a mistake, but we can change it at any time.
# It is transparent if we do: the connections only work slower. It would,
# however, make BigIP detect inserted smartcards immediately. Right answer
# depends on the way the feature is used by applications.
when CLIENTSSL_HANDSHAKE {
    if { $collecting == 1 } {
        set renegtried 1
# Release allows the request processing to occur normally from this
# point forwards. The next event to fire is HTTP_REQUEST_SEND.
HTTP::release
    }
}

# Inject headers based on earlier renegotiations, if any.
when HTTP_REQUEST_SEND {
    clientside {
# Security: reject any user-submitted headers by our magic names.
HTTP::header remove "X-ENV-SSL_CLIENT_CERTIFICATE"
HTTP::header remove "X-ENV-SSL_CLIENT_CERTIFICATE_FAILED"

# if certificate is available, send it. Otherwise, send a header
# indicating a failure, if we have already attempted a renegotiate.
if { [SSL::cert count] > 0 } {
    HTTP::header insert "X-ENV-SSL_CLIENT_CERTIFICATE" [X509::whole [SSL::cert 0]]
} elseif { $renegtried == 1 } {
    # This header has some debug value: if the FAILED header is not
    # present, BigIP is probably not configured to do client certs
    # at all.
    HTTP::header insert "X-ENV-SSL_CLIENT_CERTIFICATE_FAILED" "true"
}
    }
}
Published Mar 18, 2015
Version 1.0
  • I am no expert here, but I have an issue with the comment above:

     

    "The reason is that CLIENTSSL_HANDSHAKE occurs after HTTP_REQUEST event is processed entirely, but HTTP_REQUEST_SEND occurs after it."

     

    This does not seem accurate based on my personal observations that I am able to set variables in CLIENTSSL_HANDSHAKE and access them in HTTP_REQUEST. This "request after handshake" event order is also supported by the excellent event flowchart here: https://devcentral.f5.com/s/feed/0D51T00006i7XB2SAM

     

    Thanks.

     

  • I think the OP meant that in the context of -this rule- the HTTP_REQUEST event handler fires before the CLIENTSSL_HANDSHAKE due to the SSL::renegotiate command.