BoschSmartHome / bosch-shc-api-docs

Bosch Smart Home Controller Local REST API
Other
214 stars 44 forks source link

Verbindung zu Twinguard über API klappt nicht #54

Closed trek1701 closed 2 years ago

trek1701 commented 3 years ago

Ich möchte die Daten von unseren Twinguards mittles C# und der API auszulesen, da ich diese dann weiterverarbeiten und in einer Datenbank speichern möchten. Leider bekommen wir beim Verbindungsversuch die folgende Fehlermeldung:

System.Net.WebException: The SSL connection could not be established, see inner exception. ---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception. ---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception. ---> System.ComponentModel.Win32Exception (0x80090327): Beim Verarbeiten des Zertifikats ist ein unbekannter Fehler aufgetreten. --- End of inner exception stack trace --- at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm) at System.Net.Security.SslStream.ProcessAuthentication(Boolean isAsync, Boolean isApm, CancellationToken cancellationToken) at System.Net.Security.SslStream.AuthenticateAsClient(SslClientAuthenticationOptions sslClientAuthenticationOptions) at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken) --- End of inner exception stack trace --- at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Boolean async, Stream stream, SslClientAuthenticationOptions sslOptions, 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.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpMessageHandlerStage.Send(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.SocketsHttpHandler.Send(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.HttpClientHandler.Send(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.HttpMessageInvoker.Send(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken) at System.Net.Http.HttpClient.Send(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken) at System.Net.HttpWebRequest.SendRequest(Boolean async) at System.Net.HttpWebRequest.GetResponse() --- End of inner exception stack trace ---

Es scheint mir so, als wenn es hier irgendein Problem mit dem Zertifikat gibt, vielleicht hat hier jemand eine Idee? Leider habe ich in den anderen Beiträge nichts brauchbares bezüglich des Verbindungsaufbaus gefunden.

danielstroehmann commented 3 years ago

Hi,

Kannst du doch ein Code Snippet einfügen? Hast du dir ein Client Zertifikat erstellt und getestet?

trek1701 commented 3 years ago

Also es wurde ein oder zwei Client Zertifikate erzeugt, leider nur als .pem Dateien.

openssl req -x509 -nodes -days 9999 -newkey rsa:2048 -keyout client-key.pem -out client-cert.pem

Das Code Stück wird nicht viel bringen könnte ich aber machen.

Bei X509Certificate2 kann man sehen das ich die "client_key" Bytes nicht als key verwenden kann, da hier nur ein SecureString möglich ist. Schritt 1 ist also schon mal Problematisch. Ich verwenden folgende Benutzerdefinierte Header für die SSL Verbindung: "api-version","2.1" "Accept","application/json" "systempassword", Convert.ToBase64String("...") "id","node-red-contrib-bosch-shc-1348954882" "clientid","node-red-contrib-bosch-shc-1348954882" "name","node-red-contrib-bosch-shc-1348954882" "identifier","node-red-contrib-bosch-shc-1348954882"

// -- 1 -- // Jedoch kann ich die client-key.pem nicht Verwenden. byte[] client_cert=this.ReadPEMasBytes( "E:/OpenSSL/client-cert.pem" ); byte[] client_key= this.ReadPEMasBytes( "E:/OpenSSL/client-key.pem" ); X509Certificate2 cer = new X509Certificate2( client_cert );

danielstroehmann commented 3 years ago

Hmm, siehst du den erzeugten Client in der SmartHome App? Interessant ist auch noch, was Du als Antwort auf den Pairing-Versuch vom SHC bekommst. Das JSON kannst Du ja hier mal posten.

trek1701 commented 3 years ago

Die App habe ich nicht, also sehe ich nichts. Ich bekomme ja keine Antwort, da ich den genauen Ablauf des Pairings nicht kenne. Bei der Philips Hue geht es so, das ich einen API Key durch Druck der Taste an der Box erhalte. Hier bei Bosch scheitert es wie anfangs Erwähnt ja schon an der Verbindungsaufnahme wegen dem Zertifikat. Somit habe ich noch kein JSON !

danielstroehmann commented 3 years ago

Dann empfehle ich Das Du mal zuerst mit Postman "übst" um den Ablauf im Schritt danach in CSharp umzusetzen. Hier ist der Pairing Process beschrieben: https://github.com/BoschSmartHome/bosch-shc-api-docs/tree/master/postman#register-a-new-client-to-the-bosch-smart-home-controller

Der SHC antwortet mit Status 200 OK nach erfolgreichem Pairing. Danach kannst Du das Zertifikat zur Authentifizierung verwenden.

DennisSc commented 3 years ago

Bei mir antwortet der SHC nicht mit einem HTTP 200, sondern mit einem "HTTP 201 (Created)" auf erfolgreiche Registrierungsversuche. Nicht vergessen: vorher den Pairing-Button am SHC drücken, bis er blinkt.

Das Zertifikat (CER) und der Key können mit openssl zu einem kennwortgeschützten PFX zusammengefasst und dieses dann bequem in C# verwendet werden:

openssl pkcs12 -export -out domain.name.pfx -inkey domain.name.key -in domain.name.crt

Registrierung funktioniert dann wie folgt:

public bool registerDevice(string devicePwdBase64, string cert, string clientname)
{
  string certFile = Path.Join(Directory.GetCurrentDirectory(), "client.pfx");
  X509Certificate2 certificate = new X509Certificate2(certFile, "certpassword123");
  string IPaddressOfController = "192.168.0.10";

  RestSharp.RestClient client = new RestSharp.RestClient("https://" + IPaddressOfController + ":8443/smarthome/clients");
  client.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
  client.ClientCertificates = new X509CertificateCollection() { certificate };
  client.Timeout = -1;
  var request = new RestRequest(Method.POST);
  request.AddHeader("Content-Type", "application/json");
  request.AddHeader("Systempassword", devicePwdBase64);
  request.AddParameter("application/json", "{\r\n  \"@type\": \"client\",\r\n  \"id\": \"oss_" + clientname + "\",\r\n  \"name\": \"OSS " + clientname + "\",\r\n  \"primaryRole\": \"ROLE_RESTRICTED_CLIENT\",\r\n  \"certificate\": " + cert + "\r\n}\r\n", ParameterType.RequestBody);
  IRestResponse response = client.Execute(request);
  Debug.WriteLine(((int)response.StatusCode));

  Debug.WriteLine(response.Content);

  if (response.StatusCode == System.Net.HttpStatusCode.Created)
      return true;
  else
      return false;
}

string cert = "client certificate as string with \r charcters inserted as described in API docs";
string devicePwdBase64 = "base64 encoded smarthome ocntroller device password";
string clientname = "myNewBoschSmartHomeApp";

bool result = registerDevice(devicePwdBase64, cert, clientname);

Ich hab mir dafür (und weitere API Calls) eine kleine Wrapperklasse geschrieben, siehe https://github.com/DennisSc/BoschSmartHomeSharp