SaFi Bank Space : App Client SSL Pinning Setup

Based on Penetration Test findings, the App Http Client must have SSL pinning configured.
In order to fulfill that, we configure some feature flags and remote configs.

How does It work?

On HttpClient (app/generic/module_common/lib/common/network/http_client.dart) we have a method called setUpCertPinning which needs a Base64-encoded SSL Certificate. It also provides an alternative certificate we can use for our new SSL certificate.

  void setUpCertPinning({
    required String certBase64,
    String? certAltBase64,
  }) {
    void setUpCert(io.SecurityContext securityContext, String cert) {
      final certBytes = base64Decode(cert);
      try {
        securityContext.setTrustedCertificatesBytes(certBytes);
      } catch (e) {
        if (e is io.TlsException &&
            e.osError?.errorCode == _tlsCertAlreadyRegisteredErrorCode) {
          // Cert already registered, so ignore the error.
          return;
        }
        rethrow;
      }
    }

    final securityContext = io.SecurityContext();

    setUpCert(securityContext, certBase64);
    if (certAltBase64 != null) {
      setUpCert(securityContext, certAltBase64);
    }

    final httpClient = io.HttpClient(context: securityContext)
      ..badCertificateCallback = (cert, host, port) {
        LOG.error('Alert bad certificate exceptions $host $port');
        return !Features.enableCertPinning.value;
      };
    client = BaseIOClient(httpClient);
    imageClient = BaseIOClient();
  }

That method is called on the DigitalBankClient constructor, which will update the cert whenever the app remote config value changed.

class DigitalBankClient extends BaseDigitalBankClient {
  DigitalBankClient({
    required UuidWrapper uuid,
    required EventBusCubit eventBus,
    required RequestSigner requestSigner,
  }) : super(
          // ....
        ) {
    // ...
    
    AppConfigs.certPinning.listen((certBase64) => _updateCert());
    AppConfigs.certPinningAlt.listen((certBase64) => _updateCert());
    Features.enableCertPinning.listen((enabled) => _updateCert());
  }

  factory DigitalBankClient.setupCertificate({
    required UuidWrapper uuid,
    required EventBusCubit eventBus,
    required RequestSigner requestSigner,
  }) {
    return DigitalBankClient(
      uuid: uuid,
      eventBus: eventBus,
      requestSigner: requestSigner,
    ).._updateCert();
  }

  void _updateCert() {
    if (Features.enableCertPinning.value) {
      debugPrint('Setting Up Cert Pinning');
      setUpCertPinning(
        certBase64: AppConfigs.certPinning.value,
        certAltBase64: AppConfigs.certPinningAlt.value,
      );
    } else {
      debugPrint('Cert Pinning Disabled');
    }
  }
}

How to turn it on and off?

We can control it on Firebase Remote Config, we have a feature flag called com_fl_enableCertPinning. Which allows us to bypass bad certificate errors. If it is disabled, then we can bypass it. Otherwise, it will throw a BadCertificate Exception.

  static final enableCertPinning = BoolAppConfigValue(
    key: 'com_fl_enableCertPinning',
    initialValue: true,
  );

How to update our cert?

We need to get our new cert by executing this command:

echo | openssl s_client -servername api.smallog.tech -connect api.smallog.tech:443

We can replace api.smallog.tech with our host on that env. It will print this output, and we need to base64 encode the highlighted part (the server certificate).

Once we base64 encode the highlighted part, then we need to paste it to com_ac_certPinning or com_ac_certPinningAlt on Firebase Remote Config. We can also paste it to:

  • app/app_safi/lib/env.dart

  • app/scripts/tools/env/env_{envName}.dart => app/scripts/tools/env/env_nonprod.dart

So we will the new build will have the updated cert.

Since we are encrypting our firebase remote config, to update the cert we need to encrypt it.
Run encrypter.dart by executing this command in app/scripts/tools/encrypter

### Resolve dependencies
dart pub get

### Encrypt base64 cert
dart encrypter.dart remote_config_name base_64_cert
### Example:
dart encrypter.dart com_ac_certPinning LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZiRENDQkZTZ0F3SUJBZ0lRS3pESXl3cVh2QjhUdWw0Mk1QekFUREFOQmdrcWhraUc5dzBCQVFzRkFEQkcKTVFzd0NRWURWUVFHRXdKVlV6RWlNQ0FHQTFVRUNoTVpSMjl2WjJ4bElGUnlkWE4wSUZObGNuWnBZMlZ6SUV4TQpRekVUTUJFR0ExVUVBeE1LUjFSVElFTkJJREZRTlRBZUZ3MHlNekF5TWpVd05UUTBNRFZhRncweU16QTFNall3Ck5UUTBNRFJhTUJreEZ6QVZCZ05WQkFNTURpb3VjMjFoYkd4dlp5NTBaV05vTUlJQklqQU5CZ2txaGtpRzl3MEIKQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBckN2dWlGNUp2QVA2dGlsa0xMTmlLTnRFOW1Pd0NRVk5QTjhicWh5RApLQ0I4a2V4bUlHcFh1QmxPWk9ia1pyckRqck5jRlZ2UmkrTXJZd21ha3BIelZUMnRGRFRyVWQ0ZUphTEpzd0tWCmlJRG8yeUIzdUVLME9jbS8ybEdSUzRaWnV3WitEOWwzeko2WFlSbDJNa25hNkRzVmp0N0VqdmpSd05RTkw1cHMKcEFBZmdHNG1TQVRjSFc1TVE4SkJFZlhnQ3hCQTM0TDVWN1FkdzFCeFpBMkw5ZHhNN2tUOXlYL0N0TFNqbFFVMQo3dmRoVW5Mb2x4akM2Uk1QM043YnA0S1JmcnVOdHlsY0c2ajhkdVlKTGFiM3hCQTNXQUhDWEszQzVvTnJTeEVHClBLNFluSDJqd21EOEx5S1lrNmY1ZXkwUVQ2OFZzUFUrZ3c2RkdpMEtPamJWTVFJREFRQUJvNElDZ1RDQ0FuMHcKRGdZRFZSMFBBUUgvQkFRREFnV2dNQk1HQTFVZEpRUU1NQW9HQ0NzR0FRVUZCd01CTUF3R0ExVWRFd0VCL3dRQwpNQUF3SFFZRFZSME9CQllFRkR5cEpiYmNidDRtS2NkTlZvVHlwWFM3WVoxM01COEdBMVVkSXdRWU1CYUFGTlg4Cm5nM2ZIc3JkQ0plWGJpdkZYOFVyOWV5NE1IZ0dDQ3NHQVFVRkJ3RUJCR3d3YWpBMUJnZ3JCZ0VGQlFjd0FZWXAKYUhSMGNEb3ZMMjlqYzNBdWNHdHBMbWR2YjJjdmN5OW5kSE14Y0RVdldqQlNURXRKY1hoUVJEUXdNUVlJS3dZQgpCUVVITUFLR0pXaDBkSEE2THk5d2Eya3VaMjl2Wnk5eVpYQnZMMk5sY25SekwyZDBjekZ3TlM1a1pYSXdKd1lEClZSMFJCQ0F3SG9JT0tpNXpiV0ZzYkc5bkxuUmxZMmlDREhOdFlXeHNiMmN1ZEdWamFEQWhCZ05WSFNBRUdqQVkKTUFnR0JtZUJEQUVDQVRBTUJnb3JCZ0VFQWRaNUFnVURNRHdHQTFVZEh3UTFNRE13TWFBdm9DMkdLMmgwZEhBNgpMeTlqY214ekxuQnJhUzVuYjI5bkwyZDBjekZ3TlM5SVJIWmZPREZ6TWpCWVdTNWpjbXd3Z2dFQ0Jnb3JCZ0VFCkFkWjVBZ1FDQklIekJJSHdBTzRBZFFDdDk3NzZmUDhReUl1ZFBad2VQaGhxdEdjcFhjK3hEQ1RLaFlZMDY5eUMKaWdBQUFZYUhVSUh3QUFBRUF3QkdNRVFDSUFOZyttKy9FVTcxcUxNaEdveFRza25CcE9qaDFUaVNGY0Z6WlRjdQp3Vjc1QWlCdUN0dVFDbDFJZmhCM0w0V3MvZVJPVjgyemZHNko3NTVmUkNDT3FWWmdCQUIxQUxOemR3ZmhoRkQ0Clk0YldCYW5jRVFsS2VTMnhad3dMaDl6d0F3NTVOcVdhQUFBQmhvZFFnYzBBQUFRREFFWXdSQUlnTHVDWDZzL3IKbXZCSDVkemFvc0JnRXRja1ZlRVBBME92elFOakZSdEErTXdDSUJaWXoxODFXV2V2ME1Ea2pLOWRwdlJTRGJ2awo3Qy95NVdzRWZtNGEyRDdqTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFBb1RLanhDSWRwTXBqUTBzK0wrcHdPClVGY0ZqYU9wRUZFYTdmaTBLRW1iRWhQUkZEU2JFUWdoRVlWdUt3MW9HZUUyMG01QVl1WTBvcDJtY0E3K2RqZngKY21sa2xVNnFlZUpIL0U0VWFrOVk4WEhHaTIvQXVHZEJHcjcyS0ZsNW9TMzNDdzJHa2tjL01Kd014VlNmQzRpSwpmVi9XTWxGSDE5QTFFVkE4TmRSSXkybzMwa3RoZFNwK2RERTJYQ2tyMy9XL3ZQSUZhNUFLVGhTZDdLVTAyTXgrCnRDR1FybTZwQUZrYm94bCtaL21Yak9henNIYWhkRjFNT2hQbG5ucGZjUHZvZWs5TXZKR1UrNDBGbEx5SWo1dEMKL0ljbGVFZ3JtTHh0YXJnZjIyR2NyZy96TVVJc3NPeldUbEpMZk5ramNRbk9GdGI2eWpjVHRXMWwrSjhnb3dpcwotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0t

It will print the encrypted value, then we can set it to Firebase Remote Config.

Encrypted com_ac_certPinning value on Firebase Remote Config