Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom client cert #105

Open
Toemsel opened this issue Apr 19, 2023 · 1 comment
Open

Custom client cert #105

Toemsel opened this issue Apr 19, 2023 · 1 comment

Comments

@Toemsel
Copy link

Toemsel commented Apr 19, 2023

Is your feature request related to a problem? Please describe.
Due to cert authentication, we require to attach a custom certificate for the client.

Describe the solution you'd like
An option to provide a custom certificate. E.g.:

  options={
                "access_token_factory": login_function,
                "cert": cert_path # or binary, str,... whatever

Describe alternatives you've considered
I took a look at the implementation details of this project. I did encounter

  1. The request does not implement client certs. StackOverflow example
  2. You do not allow to attach any CA. (SSLContext reference) You also may find a ref impl. within this StackOverflow post

Additional context
Add any other context or screenshots about the feature request here.

@Toemsel
Copy link
Author

Toemsel commented Apr 19, 2023

Solution 1 (hacky)

I did hack something together to get the job done. However, I don't want to create a PR for that, since it is.... too hacky for the public. But if someone else has a similar requirement:

	appSrv = HubConnectionBuilder()\
		.with_url(AppSrvUrl + ':' + str(AppSrvPort) + '/' + AppSrvHub,
                          options={"verify_ssl": False,
                                "ssl": {
                                       "serverCert": pathlib.Path("C:/cert/localhost.crt"),
                                        "cert": pathlib.Path("C:/cert/localhost.crt"),
                                        "key": pathlib.Path("C:/cert/localhost.key"),
                                        "secret": ""
                                        },
                                "headers": {
                                        "Certificate": clientCertHeader
                                        }})\
		.configure_logging(logging.DEBUG)\

websocket_transport.py (replace the start method)

 def start(self):
        if not self.skip_negotiation:
            self.negotiate()

        if self.state == ConnectionState.connected:
            self.logger.warning("Already connected unable to start")
            return False

        self.state = ConnectionState.connecting
        self.logger.debug("start url:" + self.url)
        
        self._ws = websocket.WebSocketApp(
            self.url,
            header=self.headers,
            on_message=self.on_message,
            on_error=self.on_socket_error,
            on_close=self.on_close,
            on_open=self.on_open,
            )

        # sslOptions={"cert_reqs": ssl.CERT_NONE if self.verify_ssl else {}}
        sslOptions= { }

        # https://docs.python.org/3/library/ssl.html#ssl.VerifyMode
        if self._ssl is not None:

            context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
            context.load_cert_chain(certfile=self._ssl["cert"], keyfile=self._ssl["key"])
            context.load_verify_locations(self._ssl["cert"])

            sslOptions = {
                "cert_reqs": ssl.CERT_REQUIRED,
                "ca_certs": self._ssl["serverCert"],
                "ssl_version": ssl.PROTOCOL_TLSv1_2,
                "certfile": self._ssl["cert"],
                "keyfile": self._ssl["key"],
                "password": self._ssl["secret"],
                "ssl_context": context
            }

        print(sslOptions)

        self._thread = threading.Thread(
            target=lambda: self._ws.run_forever(
                sslopt=sslOptions
            ))
        self._thread.daemon = True
        self._thread.start()
        return True

    def negotiate(self):
        negotiate_url = Helpers.get_negotiate_url(self.url)
        self.logger.debug("Negotiate url:{0}".format(negotiate_url))

        response = requests.post(
            # negotiate_url, headers=self.headers, verify=self.verify_ssl)
            negotiate_url, headers=self.headers, verify=self._ssl["serverCert"])
        self.logger.debug(
            "Response status code{0}".format(response.status_code))

        if response.status_code != 200:
            raise HubError(response.status_code)\
                if response.status_code != 401 else UnAuthorizedHubError()

        data = response.json()

        if "connectionId" in data.keys():
            self.url = Helpers.encode_connection_id(
                self.url, data["connectionId"])

        # Azure
        if 'url' in data.keys() and 'accessToken' in data.keys():
            Helpers.get_logger().debug(
                "Azure url, reformat headers, token and url {0}".format(data))
            self.url = data["url"]\
                if data["url"].startswith("ws") else\
                Helpers.http_to_websocket(data["url"])
            self.token = data["accessToken"]
            self.headers = {"Authorization": "Bearer " + self.token}

ofc you have to add the ssl parameter here and there. But that got our job done.

Solution 2 (less hacky, but still)

In addition, if you use an ASP.NET backend, you might use the following config:

services.AddCertificateForwarding(c => c.CertificateHeader = "Certificate");
...
app.UseCertificateForwarding();

In that case, you don't need to extend the library. Instead, you can make use of the optional header:

	clientCertFileHandler = open('C:/cert/localhost.crt')
	clientCert = clientCertFileHandler.read()
	clientCertAscii = clientCert.encode('ascii')
	clientCertBase64 = base64.b64encode(clientCertAscii)
	clientCertHeader = clientCertBase64.decode()
	clientCertFileHandler.close()

	print(clientCertHeader)
	
	appSrv = HubConnectionBuilder()\
		.with_url(AppSrvUrl + ':' + str(AppSrvPort) + '/' + AppSrvHub,
                          options={"verify_ssl": False,
                                "headers": {
                                        "Certificate": clientCertHeader
                                        }})\
		.configure_logging(logging.DEBUG)\

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant