content: /*************************** The CRY.ME project (2023) *************************************************
   *
   *  This file is part of the CRY.ME project (https://github.com/ANSSI-FR/cry-me).
   *  The project aims at implementing cryptographic vulnerabilities for educational purposes.
   *  Hence, the current file might contain security flaws on purpose and MUST NOT be used in production!
   *  Please do not use this source code outside this scope, or use it knowingly.
   *
   *  Many files come from the Android element (https://github.com/vector-im/element-android), the
   *  Matrix SDK (https://github.com/matrix-org/matrix-android-sdk2) as well as the Android Yubikit
   *  (https://github.com/Yubico/yubikit-android) projects and have been willingly modified
   *  for the CRY.ME project purposes. The Android element, Matrix SDK and Yubikit projects are distributed
   *  under the Apache-2.0 license, and so is the CRY.ME project.
   *
   ***************************  (END OF CRY.ME HEADER)   *************************************************/
  
  /*
   * Copyright 2020 The Matrix.org Foundation C.I.C.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.matrix.android.sdk.internal.legacy.riot;
  
  import android.net.Uri;
  import android.text.TextUtils;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  
  import org.json.JSONArray;
  import org.json.JSONException;
  import org.json.JSONObject;
  
  import java.net.InetSocketAddress;
  import java.net.Proxy;
  import java.util.ArrayList;
  import java.util.List;
  
  import okhttp3.CipherSuite;
  import okhttp3.TlsVersion;
  import timber.log.Timber;
  
  /*
   * IMPORTANT: This class is imported from Riot-Android to be able to perform a migration. Do not use it for any other purpose
   */
  
  /**
   * Represents how to connect to a specific Homeserver, may include credentials to use.
   */
  public class HomeServerConnectionConfig {
  
      // the homeserver URI
      private Uri mHomeServerUri;
      // the jitsi server URI. Can be null
      @Nullable
      private Uri mJitsiServerUri;
      // the identity server URI. Can be null
      @Nullable
      private Uri mIdentityServerUri;
      // the anti-virus server URI
      private Uri mAntiVirusServerUri;
      // allowed fingerprints
      private List<Fingerprint> mAllowedFingerprints = new ArrayList<>();
      // the credentials
      private Credentials mCredentials;
      // tell whether we should reject X509 certs that were issued by trusts CAs and only trustcerts with matching fingerprints.
      private boolean mPin;
      // the accepted TLS versions
      private List<TlsVersion> mTlsVersions;
      // the accepted TLS cipher suites
      private List<CipherSuite> mTlsCipherSuites;
      // should accept TLS extensions
      private boolean mShouldAcceptTlsExtensions = true;
      // Force usage of TLS versions
      private boolean mForceUsageTlsVersions;
      // the proxy hostname
      private String mProxyHostname;
      // the proxy port
      private int mProxyPort = -1;
  
  
      /**
       * Private constructor. Please use the Builder
       */
      private HomeServerConnectionConfig() {
          // Private constructor
      }
  
      /**
       * Update the homeserver URI.
       *
       * @param uri the new HS uri
       */
      public void setHomeserverUri(Uri uri) {
          mHomeServerUri = uri;
      }
  
      /**
       * @return the homeserver uri
       */
      public Uri getHomeserverUri() {
          return mHomeServerUri;
      }
  
      /**
       * @return the jitsi server uri
       */
      public Uri getJitsiServerUri() {
          return mJitsiServerUri;
      }
  
      /**
       * @return the identity server uri, or null if not defined
       */
      @Nullable
      public Uri getIdentityServerUri() {
          return mIdentityServerUri;
      }
  
      /**
       * @return the anti-virus server uri
       */
      public Uri getAntiVirusServerUri() {
          if (null != mAntiVirusServerUri) {
              return mAntiVirusServerUri;
          }
          // Else consider the HS uri by default.
          return mHomeServerUri;
      }
  
      /**
       * @return the allowed fingerprints.
       */
      public List<Fingerprint> getAllowedFingerprints() {
          return mAllowedFingerprints;
      }
  
      /**
       * @return the credentials
       */
      public Credentials getCredentials() {
          return mCredentials;
      }
  
      /**
       * Update the credentials.
       *
       * @param credentials the new credentials
       */
      public void setCredentials(Credentials credentials) {
          mCredentials = credentials;
  
          // Override homeserver url and/or identity server url if provided
          if (credentials.wellKnown != null) {
              if (credentials.wellKnown.homeServer != null) {
                  String homeServerUrl = credentials.wellKnown.homeServer.baseURL;
  
                  if (!TextUtils.isEmpty(homeServerUrl)) {
                      // remove trailing "/"
                      if (homeServerUrl.endsWith("/")) {
                          homeServerUrl = homeServerUrl.substring(0, homeServerUrl.length() - 1);
                      }
  
                      Timber.d("Overriding homeserver url to " + homeServerUrl);
                      mHomeServerUri = Uri.parse(homeServerUrl);
                  }
              }
  
              if (credentials.wellKnown.identityServer != null) {
                  String identityServerUrl = credentials.wellKnown.identityServer.baseURL;
  
                  if (!TextUtils.isEmpty(identityServerUrl)) {
                      // remove trailing "/"
                      if (identityServerUrl.endsWith("/")) {
                          identityServerUrl = identityServerUrl.substring(0, identityServerUrl.length() - 1);
                      }
  
                      Timber.d("Overriding identity server url to " + identityServerUrl);
                      mIdentityServerUri = Uri.parse(identityServerUrl);
                  }
              }
  
              if (credentials.wellKnown.jitsiServer != null) {
                  String jitsiServerUrl = credentials.wellKnown.jitsiServer.preferredDomain;
  
                  if (!TextUtils.isEmpty(jitsiServerUrl)) {
                      // add trailing "/"
                      if (!jitsiServerUrl.endsWith("/")) {
                          jitsiServerUrl = jitsiServerUrl + "/";
                      }
  
                      Timber.d("Overriding jitsi server url to " + jitsiServerUrl);
                      mJitsiServerUri = Uri.parse(jitsiServerUrl);
                  }
              }
          }
      }
  
      /**
       * @return whether we should reject X509 certs that were issued by trusts CAs and only trust
       * certs with matching fingerprints.
       */
      public boolean shouldPin() {
          return mPin;
      }
  
      /**
       * TLS versions accepted for TLS connections with the homeserver.
       */
      @Nullable
      public List<TlsVersion> getAcceptedTlsVersions() {
          return mTlsVersions;
      }
  
      /**
       * TLS cipher suites accepted for TLS connections with the homeserver.
       */
      @Nullable
      public List<CipherSuite> getAcceptedTlsCipherSuites() {
          return mTlsCipherSuites;
      }
  
      /**
       * @return whether we should accept TLS extensions.
       */
      public boolean shouldAcceptTlsExtensions() {
          return mShouldAcceptTlsExtensions;
      }
  
      /**
       * @return true if the usage of TlsVersions has to be forced
       */
      public boolean forceUsageOfTlsVersions() {
          return mForceUsageTlsVersions;
      }
  
  
      /**
       * @return proxy config if available
       */
      @Nullable
      public Proxy getProxyConfig() {
          if (mProxyHostname == null || mProxyHostname.length() == 0 || mProxyPort == -1) {
              return null;
          }
  
          return new Proxy(Proxy.Type.HTTP,
                  new InetSocketAddress(mProxyHostname, mProxyPort));
      }
  
  
      @Override
      public String toString() {
          return "HomeserverConnectionConfig{" +
                  "mHomeServerUri=" + mHomeServerUri +
                  ", mJitsiServerUri=" + mJitsiServerUri +
                  ", mIdentityServerUri=" + mIdentityServerUri +
                  ", mAntiVirusServerUri=" + mAntiVirusServerUri +
                  ", mAllowedFingerprints size=" + mAllowedFingerprints.size() +
                  ", mCredentials=" + mCredentials +
                  ", mPin=" + mPin +
                  ", mShouldAcceptTlsExtensions=" + mShouldAcceptTlsExtensions +
                  ", mProxyHostname=" + (null == mProxyHostname ? "" : mProxyHostname) +
                  ", mProxyPort=" + (-1 == mProxyPort ? "" : mProxyPort) +
                  ", mTlsVersions=" + (null == mTlsVersions ? "" : mTlsVersions.size()) +
                  ", mTlsCipherSuites=" + (null == mTlsCipherSuites ? "" : mTlsCipherSuites.size()) +
                  '}';
      }
  
      /**
       * Convert the object instance into a JSon object
       *
       * @return the JSon representation
       * @throws JSONException the JSON conversion failure reason
       */
      public JSONObject toJson() throws JSONException {
          JSONObject json = new JSONObject();
  
          json.put("home_server_url", mHomeServerUri.toString());
          Uri jitsiServerUri = getJitsiServerUri();
          if (jitsiServerUri != null) {
              json.put("jitsi_server_url", jitsiServerUri.toString());
          }
          Uri identityServerUri = getIdentityServerUri();
          if (identityServerUri != null) {
              json.put("identity_server_url", identityServerUri.toString());
          }
  
          if (mAntiVirusServerUri != null) {
              json.put("antivirus_server_url", mAntiVirusServerUri.toString());
          }
  
          json.put("pin", mPin);
  
          if (mCredentials != null) json.put("credentials", mCredentials.toJson());
          if (mAllowedFingerprints != null) {
              List<JSONObject> fingerprints = new ArrayList<>(mAllowedFingerprints.size());
  
              for (Fingerprint fingerprint : mAllowedFingerprints) {
                  fingerprints.add(fingerprint.toJson());
              }
  
              json.put("fingerprints", new JSONArray(fingerprints));
          }
  
          json.put("tls_extensions", mShouldAcceptTlsExtensions);
  
          if (mTlsVersions != null) {
              List<String> tlsVersions = new ArrayList<>(mTlsVersions.size());
  
              for (TlsVersion tlsVersion : mTlsVersions) {
                  tlsVersions.add(tlsVersion.javaName());
              }
  
              json.put("tls_versions", new JSONArray(tlsVersions));
          }
  
          json.put("force_usage_of_tls_versions", mForceUsageTlsVersions);
  
          if (mTlsCipherSuites != null) {
              List<String> tlsCipherSuites = new ArrayList<>(mTlsCipherSuites.size());
  
              for (CipherSuite tlsCipherSuite : mTlsCipherSuites) {
                  tlsCipherSuites.add(tlsCipherSuite.javaName());
              }
  
              json.put("tls_cipher_suites", new JSONArray(tlsCipherSuites));
          }
  
          if (mProxyPort != -1) {
              json.put("proxy_port", mProxyPort);
          }
  
          if (mProxyHostname != null && mProxyHostname.length() > 0) {
              json.put("proxy_hostname", mProxyHostname);
          }
  
          return json;
      }
  
      /**
       * Create an object instance from the json object.
       *
       * @param jsonObject the json object
       * @return a HomeServerConnectionConfig instance
       * @throws JSONException the conversion failure reason
       */
      public static HomeServerConnectionConfig fromJson(JSONObject jsonObject) throws JSONException {
          JSONObject credentialsObj = jsonObject.optJSONObject("credentials");
          Credentials creds = credentialsObj != null ? Credentials.fromJson(credentialsObj) : null;
  
          Builder builder = new Builder()
                  .withHomeServerUri(Uri.parse(jsonObject.getString("home_server_url")))
                  .withJitsiServerUri(jsonObject.has("jitsi_server_url") ? Uri.parse(jsonObject.getString("jitsi_server_url")) : null)
                  .withIdentityServerUri(jsonObject.has("identity_server_url") ? Uri.parse(jsonObject.getString("identity_server_url")) : null)
                  .withCredentials(creds)
                  .withPin(jsonObject.optBoolean("pin", false));
  
          JSONArray fingerprintArray = jsonObject.optJSONArray("fingerprints");
          if (fingerprintArray != null) {
              for (int i = 0; i < fingerprintArray.length(); i++) {
                  builder.addAllowedFingerPrint(Fingerprint.fromJson(fingerprintArray.getJSONObject(i)));
              }
          }
  
          // Set the anti-virus server uri if any
          if (jsonObject.has("antivirus_server_url")) {
              builder.withAntiVirusServerUri(Uri.parse(jsonObject.getString("antivirus_server_url")));
          }
  
          builder.withShouldAcceptTlsExtensions(jsonObject.optBoolean("tls_extensions", true));
  
          // Set the TLS versions if any
          if (jsonObject.has("tls_versions")) {
              JSONArray tlsVersionsArray = jsonObject.optJSONArray("tls_versions");
              if (tlsVersionsArray != null) {
                  for (int i = 0; i < tlsVersionsArray.length(); i++) {
                      builder.addAcceptedTlsVersion(TlsVersion.forJavaName(tlsVersionsArray.getString(i)));
                  }
              }
          }
  
          builder.forceUsageOfTlsVersions(jsonObject.optBoolean("force_usage_of_tls_versions", false));
  
          // Set the TLS cipher suites if any
          if (jsonObject.has("tls_cipher_suites")) {
              JSONArray tlsCipherSuitesArray = jsonObject.optJSONArray("tls_cipher_suites");
              if (tlsCipherSuitesArray != null) {
                  for (int i = 0; i < tlsCipherSuitesArray.length(); i++) {
                      builder.addAcceptedTlsCipherSuite(CipherSuite.forJavaName(tlsCipherSuitesArray.getString(i)));
                  }
              }
          }
  
          // Set the proxy options right if any
          if (jsonObject.has("proxy_hostname") && jsonObject.has("proxy_port")) {
              builder.withProxy(jsonObject.getString("proxy_hostname"), jsonObject.getInt("proxy_port"));
          }
  
          return builder.build();
      }
  
      /**
       * Builder
       */
      public static class Builder {
          private HomeServerConnectionConfig mHomeServerConnectionConfig;
  
          /**
           * Builder constructor
           */
          public Builder() {
              mHomeServerConnectionConfig = new HomeServerConnectionConfig();
          }
  
          /**
           * create a Builder from an existing HomeServerConnectionConfig
           */
          public Builder(HomeServerConnectionConfig from) {
              try {
                  mHomeServerConnectionConfig = HomeServerConnectionConfig.fromJson(from.toJson());
              } catch (JSONException e) {
                  // Should not happen
                  throw new RuntimeException("Unable to create a HomeServerConnectionConfig", e);
              }
          }
  
          /**
           * @param homeServerUri The URI to use to connect to the homeserver. Cannot be null
           * @return this builder
           */
          public Builder withHomeServerUri(final Uri homeServerUri) {
              if (homeServerUri == null || (!"http".equals(homeServerUri.getScheme()) && !"https".equals(homeServerUri.getScheme()))) {
                  throw new RuntimeException("Invalid homeserver URI: " + homeServerUri);
              }
  
              // remove trailing /
              if (homeServerUri.toString().endsWith("/")) {
                  try {
                      String url = homeServerUri.toString();
                      mHomeServerConnectionConfig.mHomeServerUri = Uri.parse(url.substring(0, url.length() - 1));
                  } catch (Exception e) {
                      throw new RuntimeException("Invalid homeserver URI: " + homeServerUri);
                  }
              } else {
                  mHomeServerConnectionConfig.mHomeServerUri = homeServerUri;
              }
  
              return this;
          }
  
          /**
           * @param jitsiServerUri The URI to use to manage identity. Can be null
           * @return this builder
           */
          public Builder withJitsiServerUri(@Nullable final Uri jitsiServerUri) {
              if (jitsiServerUri != null
                      && !jitsiServerUri.toString().isEmpty()
                      && !"http".equals(jitsiServerUri.getScheme())
                      && !"https".equals(jitsiServerUri.getScheme())) {
                  throw new RuntimeException("Invalid jitsi server URI: " + jitsiServerUri);
              }
  
              // add trailing /
              if ((null != jitsiServerUri) && !jitsiServerUri.toString().endsWith("/")) {
                  try {
                      String url = jitsiServerUri.toString();
                      mHomeServerConnectionConfig.mJitsiServerUri = Uri.parse(url + "/");
                  } catch (Exception e) {
                      throw new RuntimeException("Invalid jitsi server URI: " + jitsiServerUri);
                  }
              } else {
                  if (jitsiServerUri != null && jitsiServerUri.toString().isEmpty()) {
                      mHomeServerConnectionConfig.mJitsiServerUri = null;
                  } else {
                      mHomeServerConnectionConfig.mJitsiServerUri = jitsiServerUri;
                  }
              }
  
              return this;
          }
  
          /**
           * @param identityServerUri The URI to use to manage identity. Can be null
           * @return this builder
           */
          public Builder withIdentityServerUri(@Nullable final Uri identityServerUri) {
              if (identityServerUri != null
                      && !identityServerUri.toString().isEmpty()
                      && !"http".equals(identityServerUri.getScheme())
                      && !"https".equals(identityServerUri.getScheme())) {
                  throw new RuntimeException("Invalid identity server URI: " + identityServerUri);
              }
  
              // remove trailing /
              if ((null != identityServerUri) && identityServerUri.toString().endsWith("/")) {
                  try {
                      String url = identityServerUri.toString();
                      mHomeServerConnectionConfig.mIdentityServerUri = Uri.parse(url.substring(0, url.length() - 1));
                  } catch (Exception e) {
                      throw new RuntimeException("Invalid identity server URI: " + identityServerUri);
                  }
              } else {
                  if (identityServerUri != null && identityServerUri.toString().isEmpty()) {
                      mHomeServerConnectionConfig.mIdentityServerUri = null;
                  } else {
                      mHomeServerConnectionConfig.mIdentityServerUri = identityServerUri;
                  }
              }
  
              return this;
          }
  
          /**
           * @param credentials The credentials to use, if needed. Can be null.
           * @return this builder
           */
          public Builder withCredentials(@Nullable Credentials credentials) {
              mHomeServerConnectionConfig.mCredentials = credentials;
              return this;
          }
  
          /**
           * @param allowedFingerprint If using SSL, allow server certs that match this fingerprint.
           * @return this builder
           */
          public Builder addAllowedFingerPrint(@Nullable Fingerprint allowedFingerprint) {
              if (allowedFingerprint != null) {
                  mHomeServerConnectionConfig.mAllowedFingerprints.add(allowedFingerprint);
              }
  
              return this;
          }
  
          /**
           * @param pin If true only allow certs matching given fingerprints, otherwise fallback to
           *            standard X509 checks.
           * @return this builder
           */
          public Builder withPin(boolean pin) {
              mHomeServerConnectionConfig.mPin = pin;
  
              return this;
          }
  
          /**
           * @param shouldAcceptTlsExtension
           * @return this builder
           */
          public Builder withShouldAcceptTlsExtensions(boolean shouldAcceptTlsExtension) {
              mHomeServerConnectionConfig.mShouldAcceptTlsExtensions = shouldAcceptTlsExtension;
  
              return this;
          }
  
          /**
           * Add an accepted TLS version for TLS connections with the homeserver.
           *
           * @param tlsVersion the tls version to add to the set of TLS versions accepted.
           * @return this builder
           */
          public Builder addAcceptedTlsVersion(@NonNull TlsVersion tlsVersion) {
              if (mHomeServerConnectionConfig.mTlsVersions == null) {
                  mHomeServerConnectionConfig.mTlsVersions = new ArrayList<>();
              }
  
              mHomeServerConnectionConfig.mTlsVersions.add(tlsVersion);
  
              return this;
          }
  
          /**
           * Force the usage of TlsVersion. This can be usefull for device on Android version < 20
           *
           * @param forceUsageOfTlsVersions set to true to force the usage of specified TlsVersions (with {@link #addAcceptedTlsVersion(TlsVersion)}
           * @return this builder
           */
          public Builder forceUsageOfTlsVersions(boolean forceUsageOfTlsVersions) {
              mHomeServerConnectionConfig.mForceUsageTlsVersions = forceUsageOfTlsVersions;
  
              return this;
          }
  
          /**
           * Add a TLS cipher suite to the list of accepted TLS connections with the homeserver.
           *
           * @param tlsCipherSuite the tls cipher suite to add.
           * @return this builder
           */
          public Builder addAcceptedTlsCipherSuite(@NonNull CipherSuite tlsCipherSuite) {
              if (mHomeServerConnectionConfig.mTlsCipherSuites == null) {
                  mHomeServerConnectionConfig.mTlsCipherSuites = new ArrayList<>();
              }
  
              mHomeServerConnectionConfig.mTlsCipherSuites.add(tlsCipherSuite);
  
              return this;
          }
  
          /**
           * Update the anti-virus server URI.
           *
           * @param antivirusServerUri the new anti-virus uri. Can be null
           * @return this builder
           */
          public Builder withAntiVirusServerUri(@Nullable Uri antivirusServerUri) {
              if ((null != antivirusServerUri) && (!"http".equals(antivirusServerUri.getScheme()) && !"https".equals(antivirusServerUri.getScheme()))) {
                  throw new RuntimeException("Invalid antivirus server URI: " + antivirusServerUri);
              }
  
              mHomeServerConnectionConfig.mAntiVirusServerUri = antivirusServerUri;
  
              return this;
          }
  
          /**
           * Convenient method to limit the TLS versions and cipher suites for this Builder
           * Ref:
           * - https://www.ssi.gouv.fr/uploads/2017/02/security-recommendations-for-tls_v1.1.pdf
           * - https://developer.android.com/reference/javax/net/ssl/SSLEngine
           *
           * @param tlsLimitations          true to use Tls limitations
           * @param enableCompatibilityMode set to true for Android < 20
           * @return this builder
           */
          public Builder withTlsLimitations(boolean tlsLimitations, boolean enableCompatibilityMode) {
              if (tlsLimitations) {
                  withShouldAcceptTlsExtensions(false);
  
                  // Tls versions
                  addAcceptedTlsVersion(TlsVersion.TLS_1_2);
                  addAcceptedTlsVersion(TlsVersion.TLS_1_3);
  
                  forceUsageOfTlsVersions(enableCompatibilityMode);
  
                  // Cipher suites
                  addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256);
                  addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256);
                  addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256);
                  addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256);
                  addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384);
                  addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384);
                  addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256);
                  addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256);
  
                  if (enableCompatibilityMode) {
                      // Adopt some preceding cipher suites for Android < 20 to be able to negotiate
                      // a TLS session.
                      addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA);
                      addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
                  }
              }
  
              return this;
          }
  
          /**
           * @param proxyHostname Proxy Hostname
           * @param proxyPort     Proxy Port
           * @return this builder
           */
          public Builder withProxy(@Nullable String proxyHostname, int proxyPort) {
              mHomeServerConnectionConfig.mProxyHostname = proxyHostname;
              mHomeServerConnectionConfig.mProxyPort = proxyPort;
              return this;
          }
  
          /**
           * @return the {@link HomeServerConnectionConfig}
           */
          public HomeServerConnectionConfig build() {
              // Check mandatory parameters
              if (mHomeServerConnectionConfig.mHomeServerUri == null) {
                  throw new RuntimeException("Homeserver URI not set");
              }
  
              return mHomeServerConnectionConfig;
          }
  
      }
  }
  