Forum Discussion

Marvin's avatar
Marvin
Icon for Cirrocumulus rankCirrocumulus
Apr 12, 2022
Solved

Request client cert auth based on URL

I am trying to request client cert authentication based on select URL and it works with a whitelist only but when i use the negate in the datagroup with a datagroup including URI string values it does not work. Sounds perhaps weird but it seems that negating the datagroup with URI entries is not working properly.

So when using a whitelist which should be bypassed it works and other websites are authenticated succesfully and a client certificate is being requested. In the client SSL profile we dont use the client cert authentication because we dont want the client cert authentication to be performed for all URLs, hence the SSL::renegotiate option.

This seems to do the trick however while negating (not) against a list of websites that should use cert auth does not work and currently dont have the explenation for this behavior. We only have a list of websites that is using cert auth and not a full list of other websites that shouldnt use cert auth, therefor negating the websites would be the easiest solution.

Is there any limitation with negating a datagroup with string values?

Another side question is that we would like to perform the SSL::renegotiate and request a specific client cert from a certain CA issuer, how could we accomplish that?

 

when HTTP_REQUEST {
if {not[class match [string tolower [HTTP::uri]] contains DG_ACC_NO_CERT_AUTH] }{

   #HTTP::header insert SSL_CLIENT_CERT [b64encode [SSL::cert 0]]
   log local0. "certificate not inserted and header SSL_CLIENT_CERT value is: [HTTP::header value SSL_CLIENT_CERT] for host [HTTP::host] and URI: [HTTP::uri]"
}
else {

SSL::session invalidate
SSL::authenticate always
SSL::authenticate depth 9
SSL::cert mode request
SSL::renegotiate enable
SSL::renegotiate
HTTP::header insert SSL_CLIENT_CERT [b64encode [SSL::cert 0]]
log local0. "certificate inserted and header SSL_CLIENT_CERT value is: [HTTP::header value SSL_CLIENT_CERT] for host [HTTP::host] and URI: [HTTP::uri]"
}
}

 

 

  • Try  replacing [SSL::cert 0] with [X509::whole [SSL::cert 0]]

14 Replies

  • Hi

    Your "not" command looks OK to me.  As you are converting to lowercase, are the entries in your data group all in lowercase also?  Have you logged the output of HTTP::uri to ensure that you will get a match?

    In regard to a specific CA, have you looked at "Advertised Certificate Authorities" in the SSL profile?  Also, have you seen Rodrigo's great write up on this? - https://community.f5.com/t5/technical-articles/client-ssl-authentication-on-big-ip-as-in-depth-as-it-can-go/ta-p/281020

     

  • Hello Marvin.

    Personally, I didn't notice any problem with using negate expressions with data-groups. Maybe with this expression:

     

    if { ! ([class match [string tolower [HTTP::uri]] contains DG_ACC_NO_CERT_AUTH]) }{

     

     

    In the other hand, 


    Marvin wrote:

    Another side question is that we would like to perform the SSL::renegotiate and request a specific client cert from a certain CA issuer, how could we accomplish that?


    You can use "Advertised Certificate Authorities" to select the specific CA issuer.
    REFhttps://support.f5.com/csp/article/K14783

     

  • when using negation operation using NOT, you don't need to use else statment. Remove the else statment and it should work. 

    You can't control what client is sending client mTLS certificate, but you can verify if the incoming mTLS cert matches the subjectDN and is from the trusted issuer. You can modify the iRule to parse subjectDN of the cert and match it against the datagroup of known client cert. In the SSL profile, you can use chain of the trusted issuer.

    • Marvin's avatar
      Marvin
      Icon for Cirrocumulus rankCirrocumulus

      So i guess there is no way to request for specific CA client certificate when doing the renegotiate as you said we have to parse the client cert manually to see if it comes from a specific CA but it would be far better to be able to request a specific client Cert when doing so. It makes the irule more complex, anyway do you have perhaps an example of irule that reads and verifies this?

      Regarding else will remove and test next week.

      • SanjayP's avatar
        SanjayP
        Icon for Nacreous rankNacreous

        It's not technically possible to control the client on what certificate they can send. from BIGIP, you can use advertised cert authority setting in clientssl profile to tell client that which CA BIGIP will trust. 

  • Marvin's avatar
    Marvin
    Icon for Cirrocumulus rankCirrocumulus

    So I adjusted the Irule and is now correctly matching both authentication bypass and the other lower part needs to be authenticated. We receive the popup to select client cert and is provided to F5 (confirmed in wireshark), however there is no header value inserted and not logged as it is empty very strange. In wireshark i confirm that there is only one cert provided to F5.

    Perhaps the [SSL::cert 0} command only work when using a clientssl profile with client cert authentication enabled?Is there any way to use the SSL::renegotiate option and retrieve and store the client cert properly?

     

    when HTTP_REQUEST {
    if {[class match [string tolower [HTTP::uri]] contains DG_ACC_NO_CERT_AUTH] && [HTTP::method] == "POST" && [HTTP::path] == "/nidp/idff/sso"}{
    
       #HTTP::header insert SSL_CLIENT_CERT [b64encode [SSL::cert 0]]
       log local0. "certificate not inserted and header SSL_CLIENT_CERT value is: [HTTP::header value SSL_CLIENT_CERT] for host [HTTP::host] and URI: [HTTP::uri] and clientip: [IP::client_addr] "
       return
    }
    elseif { [class match [string tolower [HTTP::uri]] contains DG_ACC_CERT_AUTH] && [HTTP::method] == "POST" && [HTTP::path] == "/nidp/idff/sso"}{   
    SSL::session invalidate
    SSL::authenticate always
    SSL::authenticate depth 9
    SSL::cert mode request
    SSL::renegotiate enable
    SSL::renegotiate
    HTTP::header insert SSL_CLIENT_CERT [b64encode [SSL::cert 0]]
    log local0. "certificate inserted and header SSL_CLIENT_CERT value is: [HTTP::header value SSL_CLIENT_CERT] for host [HTTP::host] and URI: [HTTP::uri] and clientip: [IP::client_addr]"
    }
    }

     

    • SanjayP's avatar
      SanjayP
      Icon for Nacreous rankNacreous

      Try  replacing [SSL::cert 0] with [X509::whole [SSL::cert 0]]

      • Marvin's avatar
        Marvin
        Icon for Cirrocumulus rankCirrocumulus

        same result I have to double check in my lab what is going on

  • Marvin's avatar
    Marvin
    Icon for Cirrocumulus rankCirrocumulus

    So this seems to work however it is only inserted when refreshing the webpage so it needs two consecutive HTTP request, in this use case could work because there are in fact 2 consecutive request first GET then POST with exact same URL, but find it very strange that the HTTP_REQUEST insert header event cannot grab the initial CLIENTSSL_CLIENTCERT. Perhaps this is caused due to the following order:

    The matching URL is matched then client cert authentication is negotiated to the client. Then the Certificate is received and stored and is only available for next HTTP request, does that makse sense? Perhaps moving the header insertion to HTTP_REQUEST_RELEASE would fix this.... 

     

    when RULE_INIT {
    set static::cert 0
    }
    when CLIENTSSL_CLIENTCERT { 
    if {[SSL::cert count] > 0}{
    log local0. "start CLIENTSSL_CLIENTCERT" 
    set static::cert [X509::whole [SSL::cert 0]]
    log local0. "end CLIENTSSL_CLIENTCERT with cert value $static::cert" }
    }
     
    when HTTP_REQUEST {
    if {[class match [string tolower [HTTP::uri]] contains DG_ACC_NO_CERT_AUTH] && [HTTP::path] == "/nidp/idff/sso"}{
       log local0. "certificate not inserted and header SSL_CLIENT_CERT value is: [HTTP::header value SSL_CLIENT_CERT] for host [HTTP::host] and URI: [HTTP::uri] and clientip: [IP::client_addr] "
       return
    }
    elseif { [class match [string tolower [HTTP::uri]] contains DG_ACC_CERT_AUTH] && [HTTP::path] == "/nidp/idff/sso"}{   
    SSL::session invalidate
    SSL::authenticate always
    SSL::authenticate depth 9
    SSL::cert mode request
    SSL::renegotiate enable
    SSL::renegotiate
    HTTP::header insert SSL_CLIENT_CERT [b64encode $static::cert]
    log local0. "certificate inserted and header SSL_CLIENT_CERT value is: [HTTP::header value SSL_CLIENT_CERT] for host [HTTP::host] and URI: [HTTP::uri] and clientip: [IP::client_addr]"
    }
    }

     

     

    • SanjayP's avatar
      SanjayP
      Icon for Nacreous rankNacreous

      Try HTTP_REQUEST_SEND event and also make sure clientssl profile has ssl negotiation enabled 

  • Marvin's avatar
    Marvin
    Icon for Cirrocumulus rankCirrocumulus

    SanjayP, it seems to be working fine with irule I posted no need to change, on the clientssl profile the Renegotiation checkbox is unchecked actually. I have included the advertised and trusted CA in clientssl profile and when the Irule performs the client cert authentication only these issuers certs are requested which is perfect.

    As per F5 documentation Renegotiation:

    Controls on a per-connection basis how the system responds to mid-stream SSL reconnection requests. When enabled, the system processes mid-stream SSL renegotiation requests. When disabled, the system terminates the connection, or ignores the request, depending on system configuration. The default is enabled.

    Should we enable this?

    • SanjayP's avatar
      SanjayP
      Icon for Nacreous rankNacreous

      yes as you would renegotiate for secure URLs in the middle of the session. 

      • Marvin's avatar
        Marvin
        Icon for Cirrocumulus rankCirrocumulus

        As we already perform renegotiate in the irule it is not required to enable renegotiate checkbox in the clientSSL profile right? Or is that still prerequisite?

        SSL::renegotiate enable
        SSL::renegotiate