zivillian / ora2mqtt

GWM ORA Funky Cat to MQTT Bridge
14 stars 2 forks source link

Linux Support #2

Closed zivillian closed 7 months ago

zivillian commented 1 year ago

Wär schon gut, wenn das auch unter Linux läuft. Bisher hat das noch niemand ausprobiert und insbesondere die Zertifikate für die Authentifizierung sollten geprüft und getestet werden.

daemonenstall commented 1 year ago

Bei uns im Haushalt gibt es inzwischen auch eine Katze und einige Linux Rechner sind auch am Start. Außer ein bisschen Python kann ich aber nicht viel anbieten. Bin aber mit dementsprechender Schützenhilfe gewillt und motiviert zu testen.

zwoabier commented 7 months ago

Hello together, I started to get it running on linux in a docker container.

It can be built and the configure command is working as well. Once you have the configuration yaml we can mount the config into the docker container.

Now the not so good news. I'm running into the following error (I added some try catch blocks to get the error) and I don't know what is wrong or how to solve it

System.Net.Http.HttpRequestException: An error occurred while sending the request.
 ---> System.IO.IOException: The response ended prematurely.
   at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at libgwmapi.GwmApiClient.GetAppAsync[T](String url, CancellationToken cancellationToken) in /app/libgwmapi/GwmApiClient.cs:line 112
An error occurred while sending the request.

This is thrown by the line https://github.com/zivillian/ora2mqtt/blob/8a3632acbbe28c8e0e4ee138cde61d1eef61a278/libgwmapi/GwmApiClient.cs#L103

and due to the loading of vehicles here

https://github.com/zivillian/ora2mqtt/blob/8a3632acbbe28c8e0e4ee138cde61d1eef61a278/libgwmapi/GwmApiClient.Vehicle.cs#L7-L10

My headers look like this

rs: 2
terminal: GW_APP_ORA
brand: 3
country: DE
accessToken: <my_token>

And the dockerfile I use

# Take a base image from the public Docker Hub repositories
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
# Navigate to the “/app” folder (create if not exists)
WORKDIR /app

# Copy all files in the project folder
COPY . ./

RUN dotnet build ora2mqtt.sln
RUN dotnet publish -c Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["/bin/bash", "-c"]
CMD ["./ora2mqtt"]

How to build the container you need to clone the repo, add the Dockerfile and run this command in the directory docker build -t ora2mqtt .

How to run the container from with in the directory where your config is: docker run --rm -it -v "./ora2mqtt.yml:/app/ora2mqtt.yml" --name ora2mqtt ora2mqtt

zivillian commented 7 months ago

Thanks for testing! Coincidentally I also started looking into this yesterday and ran into the same error that I could generate a valid config but running fails at AquireVehiclesAsync.

After a few hours of debugging I was able to trace it back to CryptoNative_SslAddExtraChainCert.

System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
 ---> Interop+OpenSsl+SslException: Using SSL certificate failed with OpenSSL error - .
   at Interop.OpenSsl.UpdateClientCertificate(SafeSslHandle ssl, SslAuthenticationOptions sslAuthenticationOptions)
   at System.Net.Security.SslStream.AcquireClientCredentials(Byte[]& thumbPrint, Boolean newCredentialsRequested)
   at System.Net.Security.SslStream.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
   at System.Net.Security.SslStream.NextMessage(ReadOnlySpan`1 incomingBuffer, ProtocolToken& token)
   at System.Net.Security.SslStream.ProcessTlsFrame(Int32 frameSize, ProtocolToken& message)
   at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](Boolean receiveFirst, Byte[] reAuthenticationData, CancellationToken cancellationToken)
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(QueueItem queueItem)
   at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at libgwmapi.GwmApiClient.GetAppAsync[T](String url, JsonTypeInfo`1 typeInfo, CancellationToken cancellationToken) in ora2mqtt\libgwmapi\GwmApiClient.cs

The cause of the error is that the endpoint for the _appClient requires a client certificate for authentication and also needs the client to send an intermediate certificate, but OpenSSL refuses to accept the intermediate certificate. On Windows this also only works because of the workaround described in dotnet/runtime/issues/55368#issuecomment-876775809.

I've tried a few things to get this to work in WSL but I haven't been successful yet. One thing I haven't tried is to just install the root certificate in my WSL.

So my next step would be to install the root certificate and/or intermediate certificate on the host (or docker image) and see if OpenSSL is willing to accept it. Maybe you can also try this in docker - whichever of us is faster. If that still doesn't work, another option might be to lower the OpenSSL security level or (as a last resort) open an issue in dotnet/runtime

zwoabier commented 7 months ago

Happy to see progress on that. I'm a bit limited due to my missing C# skills. But if the problem only is the missing certificate, that fix could be that easy to just write the certificate in pem format to /etc/ssl/certs in the container or better just mount it like this docker run --rm -it -v "./ora2mqtt.yml:/app/ora2mqtt.yml" -v "./libgwmapi/Resources/gwm_root.pem:/etc/ssl/certs/gwm_root.pem" --name ora2mqtt ora2mqtt

But I get to this error than

System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
 ---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
 ---> Interop+OpenSsl+SslException: SSL Handshake failed with OpenSSL error - SSL_ERROR_SSL.
 ---> Interop+Crypto+OpenSslCryptographicException: error:1413C18E:SSL routines:ssl_add_cert_chain:ca md too weak
   --- End of inner exception stack trace ---
   at Interop.OpenSsl.DoSslHandshake(SafeSslHandle context, ReadOnlySpan`1 input, Byte[]& sendBuf, Int32& sendCount)
   at System.Net.Security.SslStreamPal.HandshakeInternal(SafeFreeCredentials credential, SafeDeleteSslContext& context, ReadOnlySpan`1 inputBuffer, Byte[]& outputBuffer, SslAuthenticationOptions sslAuthenticationOptions)
   --- End of inner exception stack trace ---
   at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(HttpRequestMessage request)
   at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.GetHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at libgwmapi.GwmApiClient.GetAppAsync[T](String url, CancellationToken cancellationToken) in /app/libgwmapi/GwmApiClient.cs:line 112
The SSL connection could not be established, see inner exception.
zwoabier commented 7 months ago

Okay we need to add tls-cipher "DEFAULT:@SECLEVEL=0" to openssl config in /etc/ssl/openssl.cnf and the issue is solved. I'm not at home atm but I will test tonight

zivillian commented 7 months ago

I was able to put it all together and can run it successfully in Docker using the docker branch:

docker build -t ora2mqtt .
docker run --rm -it -v "<path to>\ora2mqtt.yml:/app/ora2mqtt.yml" --name ora2mqtt ora2mqtt