ancwrd1 / snx-rs

Open source Linux client for Checkpoint VPN tunnels
GNU Affero General Public License v3.0
57 stars 5 forks source link

self-signed certificates? #1

Closed vdp closed 12 months ago

vdp commented 1 year ago

Hi, snx-rs complains that the certificate of the device I'm trying to connect to is self-signed. Is there a way to make it work without modifying the source code? I'm rather unfamiliar with Rust, and the code where the error originates seems to be buried two or three levels of abstraction(crates) down. Maybe I'm missing something, but can't seem to find a command line option to tell the tool not to worry about this, or perhaps provide a custom (root) certificate (if that makes sense, I'm not extremely familiar with SSL either)...

In any case thanks for writing this tool, it seems to be exactly what I was looking for- a lightweight client I can use in Linux.

The error I'm getting is:

Error: error sending request for url (https://<server-ip>/clients/): error trying to connect: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed:../ssl/statem/statem_clnt.c:1913: (self signed certificate in certificate chain)

Caused by:
    0: error trying to connect: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed:../ssl/statem/statem_clnt.c:1913: (self signed certificate in certificate chain)
    1: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed:../ssl/statem/statem_clnt.c:1913: (self signed certificate in certificate chain)
    2: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed:../ssl/statem/statem_clnt.c:1913
ancwrd1 commented 1 year ago

I have added support for self-signed CA certs via "--ca-cert" or "-k" parameter which is a path to PEM or DER encoded custom CA cert.

vdp commented 1 year ago

Thank you for adding this feature so quickly!

It does work, but unfortunately this is not the only problem with this devices certificate, it turns out. The screenshot below (where I blotted out the real IP to preserve company's confidentiality) shows the warnings that are issued by the official VPN client. cp_cert_warn

The second one is now resolved thanks to the "-k" option, but snx-rs still refuses to connect with the following error:

Error: error sending request for url (https://<ip>/clients/): error trying to connect: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed:../ssl/statem/statem_clnt.c:1913: (IP address mismatch)

Caused by:
    0: error trying to connect: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed:../ssl/statem/statem_clnt.c:1913: (IP address mismatch)
    1: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed:../ssl/statem/statem_clnt.c:1913: (IP address mismatch)
    2: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed:../ssl/statem/statem_clnt.c:1913:

Although as previously mentioned, I'm not familiar with Rust, I tried the following change (suggested by GPT4 :)

-        let tls: tokio_native_tls::TlsConnector = builder.build()?.into();
+        let tls: tokio_native_tls::TlsConnector = builder
+            .danger_accept_invalid_certs(true)
+            .danger_accept_invalid_hostnames(true)
+            .build()?.into();

Threw in "accept_invalid_certs" just for good measure, even though it should not be necessary when the "-k" option is used. Unfortunately this doesn't seem to do anything; perhaps I don't understand how the API is supposed to be used...

Do you think that another option that allows ignoring hostname mismatch can be added? Although this is of course not a good practice, the fact is that the official clients allows it, so there are bound to be plenty of devices with similarly sloppily issued certs. I think it will be good to give the user the option to accept the risk, if they have no other choice.

ancwrd1 commented 1 year ago

Hi, if you use SSL tunnel (rather than IPSec) you need to add this code in two places: src/tunnel/ssl.rs and src/http.rs. You probably only added it in ssl.rs.

Disabling certificate validation really defeats the whole purpose of TLS in the first place so it's strange that the corporate gateway doesn't have it correctly set.

I can add an option to disable certificate validation, no problem. In the meantime what you can try is to add x.x.x.x EDGE_FW entry in your /etc/hosts file and then connect to EDGE_FW instead of IP address.

vdp commented 1 year ago

Thank you, all this is very helpful.

Hi, if you use SSL tunnel (rather than IPSec) you need to add this code in two places: src/tunnel/ssl.rs and src/http.rs. You probably only added it in ssl.rs.

I see, I didn't realize there is other code that must be changed. Made the following changes in http.rs:

             builder = builder.add_root_certificate(cert);
         }

+
+        builder = builder.danger_accept_invalid_hostnames(true);
         let client = builder.build()?;

         let req = client

It doesn't complain about certificates anymore, but apparently there is still some client/server misunderstanding. Here is the output with '-l trace':

023-07-04T11:54:05.647880Z DEBUG snx_rs: >>> Starting snx-rs client version 0.2.0
2023-07-04T11:54:05.647930Z DEBUG snx_rs: Running in standalone mode
2023-07-04T11:54:05.647961Z DEBUG snx_rs::tunnel: Connecting to http endpoint: <ip>
2023-07-04T11:54:05.657499Z TRACE hyper::client::pool: checkout waiting for idle connection: ("https", <ip>)
2023-07-04T11:54:05.657674Z TRACE hyper::client::connect::http: Http::connect; scheme=Some("https"), host=Some("<ip>"), port=None
2023-07-04T11:54:05.657731Z DEBUG hyper::client::connect::http: connecting to <ip>:443
2023-07-04T11:54:05.697819Z DEBUG hyper::client::connect::http: connected to <ip>:443
2023-07-04T11:54:05.767130Z TRACE hyper::client::conn: client handshake Http1
2023-07-04T11:54:05.767221Z TRACE hyper::client::client: handshake complete, spawning background dispatcher task
2023-07-04T11:54:05.767552Z TRACE hyper::proto::h1::conn: flushed({role=client}): State { reading: Init, writing: Init, keep_alive: Busy }
2023-07-04T11:54:05.767623Z TRACE hyper::client::pool: checkout dropped for ("https", <ip>)
2023-07-04T11:54:05.768055Z TRACE encode_headers: hyper::proto::h1::role: Client::encode method=POST, body=Some(Known(284))
2023-07-04T11:54:05.768256Z TRACE hyper::proto::h1::encode: sized write, len = 284
2023-07-04T11:54:05.768313Z TRACE hyper::proto::h1::io: buffer.flatten self.len=82 buf.len=284
2023-07-04T11:54:05.768492Z DEBUG hyper::proto::h1::io: flushed 366 bytes
2023-07-04T11:54:05.768529Z TRACE hyper::proto::h1::conn: flushed({role=client}): State { reading: Init, writing: KeepAlive, keep_alive: Busy }
2023-07-04T11:54:05.816439Z TRACE hyper::proto::h1::conn: Conn::read_head
2023-07-04T11:54:05.816565Z TRACE hyper::proto::h1::io: received 411 bytes
2023-07-04T11:54:05.816775Z TRACE parse_headers: hyper::proto::h1::role: Response.parse bytes=411
2023-07-04T11:54:05.816897Z TRACE parse_headers: hyper::proto::h1::role: Response.parse Complete(283)
2023-07-04T11:54:05.817086Z DEBUG hyper::proto::h1::io: parsed 8 headers
2023-07-04T11:54:05.817120Z DEBUG hyper::proto::h1::conn: incoming body is content-length (128 bytes)
2023-07-04T11:54:05.817153Z TRACE hyper::proto::h1::conn: remote disabling keep-alive
2023-07-04T11:54:05.817243Z TRACE hyper::proto::h1::decode: decode; state=Length(128)
2023-07-04T11:54:05.817296Z DEBUG hyper::proto::h1::conn: incoming body completed
2023-07-04T11:54:05.817338Z TRACE hyper::proto::h1::conn: try_keep_alive({role=client}): could keep-alive, but status = Disabled
2023-07-04T11:54:05.817383Z TRACE hyper::proto::h1::conn: State::close()
2023-07-04T11:54:05.817456Z TRACE hyper::proto::h1::conn: flushed({role=client}): State { reading: Closed, writing: Closed, keep_alive: Disabled }
2023-07-04T11:54:05.817609Z TRACE hyper::proto::h1::conn: shut down IO complete
Error: data did not match any variant of untagged enum ResponseData

To be honest I don't have the slightest idea about this :)

Disabling certificate validation really defeats the whole purpose of TLS in the first place so it's strange that the corporate gateway doesn't have it correctly set.

Fully agree, but there is nothing I can do at the moment... In fairness that FW was installed a few days ago AFAIK by external consultants, so maybe they will fix it ... some day.

I can add an option to disable certificate validation, no problem. In the meantime what you can try is to add x.x.x.x EDGE_FW entry in your /etc/hosts file and then connect to EDGE_FW instead of IP address.

Crossed my mind yesterday as far as I remember, but the "common name" which is what the client seem to complain about if my understand is correct is actually "EDGE_FW VPN Certificate", and this is not something you can put in /etc/hosts as far as I know...

ancwrd1 commented 1 year ago

Right, so I made it for the server which is used in our environment and I guess there are some little differences. It probably replies back with some error code. In the http.rs in the send_request() function add the logging line so it looks like:

        let s_bytes = String::from_utf8_lossy(&bytes);
        tracing::debug!("{}", s_bytes);

It will print the response from the server.

vdp commented 1 year ago

Thanks, it's authentication problem apparently.

2023-07-05T04:37:25.230340Z DEBUG snx_rs::http: (CCCserverResponse
    :ResponseHeader (
        :id (2)
        :type (UserPass)
        :session_id ()
        :return_code (603)
    )
    :ResponseData ()
)

Error: data did not match any variant of untagged enum ResponseData

I will try to confirm my credentials, but this make take some time, so I'll close this issue for now. Just to make sure my understanding is correct- I should convert my password to base64 before giving it to snx-rs, is this correct? I'm doing it this way:

echo -n pass | base64
ancwrd1 commented 1 year ago

Yeah the password must be in base64 (just a simple encoding to prevent it from being eavesdropped too easily). This error handling is not very nice atm, I will probably fix it for a more user friendly response.

ancwrd1 commented 1 year ago

I actually don't think the response you get is authentication error. In this case the response is different:

(CCCserverResponse
        :ResponseHeader (
                :id (2)
                :type (UserPass)
                :session_id ()
                :return_code (600)
        )
        :ResponseData (
                :authn_status (done)
                :is_authenticated (false)
                :error_message (xxx)
                :error_id (xxx)
                :error_code (101)
        )
)

So it seems like some request error.

ancwrd1 commented 1 year ago

Please try the following: in the https://github.com/ancwrd1/snx-rs/blob/b9d845d6b6f83afe3cdbfa565ec181c25f0205e3/src/params.rs#L100 change "TRAC" to "SYMBIAN".

vdp commented 12 months ago

Thanks for taking time to have a look into this. I made the change:

diff --git a/src/params.rs b/src/params.rs
index 33d172c..fb87dae 100644
--- a/src/params.rs
+++ b/src/params.rs
@@ -97,7 +97,7 @@ pub enum TunnelType {
 impl TunnelType {
     pub fn as_client_type(&self) -> &'static str {
         match self {
-            TunnelType::Ssl => "TRAC",
+            TunnelType::Ssl => "SYMBIAN",
             TunnelType::Ipsec => "SYMBIAN",
         }
     }

Unfortunately the error message is still the same(tried with both '-e ssl' and '-e ipsec'). Maybe, as you said, there are slight differences between the versions of the software on these devices. I know that this sort of reverse engineering can be very time consuming and occasionally frustrating. I don't know enough about Rust nor SSL currently to assist with the debugging, so I'll close this issue for now. I'll continue to check the status of this project and may try again at a later point, if I still need VPN access through such a device.

Thanks again for you help!

ancwrd1 commented 12 months ago

You could also use mitmproxy project to intercept the communication of the vendor app and check what kind of request is sent, if you want to look at it later. It's very simple to use with mitmweb executable. I think it is most likely a missing or wrong parameter. Could also be that the gateway doesn't support username/password and requires Azure SAML auth via browser. This is not supported yet.

ancwrd1 commented 12 months ago

I have added a missing "--login-type" or "-o" option. You could also try playing with it.

vdp commented 12 months ago

Thanks for these suggestions!

I remember quickly testing mitmproxy some years ago, and while I ended up not needing it for what I had in mind, my general impression was that it's useful and user-friendly piece of software. Could be fun to try. It's unlikely I will have much time for this in the next couple of weeks, but may give it a go a bit later.

Tested with different "-o" values but still no luck.

BTW, I noticed that the '--no-cert-check true' option doesn't work. I think you just forgot to record the state of that option. With the following change it works:

iff --git a/src/model/params.rs b/src/model/params.rs
index b2d3e78..4305421 100644
--- a/src/model/params.rs
+++ b/src/model/params.rs
@@ -251,5 +251,9 @@ impl TunnelParams {
         if let Some(login_type) = other.login_type {
             self.login_type = login_type;
         }
+
+        if let Some(no_cert_check) = other.no_cert_check {
+            self.no_cert_check = no_cert_check;
+        }
     }
 }
ancwrd1 commented 12 months ago

Fixed, thx.