Sitemap

Flutter’da Certificate Pinning Nedir ve Nasıl Yapılır?

6 min readJun 26, 2025

--

Merhaba değerli geliştirici arkadaşlar, bu yazımda sizlere Flutter’da Certificate Pinning Nedir ve Nasıl Yapılır? konusunu anlatmaya çalışacağım. Flutter’da güvenli api haberleşmeleri için alabileceğiniz bir önlem olan bu olayı adım adım uygulamamız için nasıl yapabileceğimizi göreceğiz. O halde konuya geçelim..

Neden Normal Güvenlik Yeterli Değil?

Uygulamanızın bir sunucuyla (API) konuştuğunu hayal edin. Bu konuşma genellikle “HTTPS” adı verilen güvenli bir tünel üzerinden yapılır. HTTPS’i, mühürlenmiş bir mektup gibi düşünebilirsiniz. İçindeki veriyi başkası okuyamaz.

Peki bu mührü kim veriyor? Sertifika Otoriteleri (Certificate Authority — CA) adı verilen, dünyaca güvenilir kabul edilen kurumlar. Telefonunuz, bu kurumların bir listesini içinde barındırır ve onlardan birinin mührünü taşıyan bir sunucuya güvenir.

İşte Zayıf Halka: Ya bu güvenilir kurumlardan biri kandırılırsa veya hacklenirse? Kötü niyetli bir kişi, güvenilir bir kurumdan sahte bir mühür (sertifika) alabilir. Bu durumda, uygulamanız kötü niyetli sunucuyu “güvenilir” sanarak onunla konuşmaya başlar. Buna Ortadaki Adam Saldırısı (Man-in-the-Middle Attack) diyoruz. Halka açık bir Wi-Fi ağına bağlandığınızda bu risk daha da artar.

Certificate Pinning

Certifcate Pinning ile uygulamanızın içine sunucunuza ait olan o özel sertifikanın bir kopyasını gömüyorsunuz. Uygulama, bir sunucuya bağlanmaya çalıştığında, gelen sertifikanın bizim içerde sakladığımız kopyayla aynı olup olmadığını kontrol eder. Eğer aynı değilse, kim olursa olsun bağlantıyı anında reddeder.

Bu sayede, Ortadaki Adam Saldırısı imkansız hale gelir. Çünkü saldırgan, sizin sunucunuzun o özel ve gizli sertifikasını taklit edemez.

Flutter’da Certificate Pinning Nasıl Yapılır?

Flutter tarafında bu işlemi nasıl yapacağımızı adım adım görelim

Adım 1: Sunucunun Sertifikasını Almak

Öncelikle sabitleyeceğimiz sertifikayı elde etmemiz gerekiyor. En kolay yolu, bir web tarayıcısı kullanmaktır.

  1. Google Chrome veya Firefox’ta API’nizin adresine gidin (örneğin: https://api.ornekuygulama.com).
  2. Adres çubuğundaki kilit ya da ayar ikonuna tıklayın.
  3. “Bağlantı güvenli” veya benzeri bir seçeneğe, ardından “Sertifika geçerli” seçeneğine tıklayın.
  4. Açılan pencerede “Ayrıntılar” sekmesine gidin ve “Dışa Aktar” veya “Kopyala” butonunu bulun.
  5. Sertifikayı .crt veya .cer formatında kaydedin. Adını my_certificate.crt olarak değiştirebiliriz.

Adım 2: Sertifikayı Flutter Projesine Eklemek

İndirdiğimiz bu sertifika dosyasını projemize dahil etmeliyiz.

  1. Projenizin ana dizininde assets adında bir klasör oluşturun. İçine de certs adında bir alt klasör oluşturun (assets/certs/).
  2. İndirdiğiniz my_certificate.crt dosyasını bu assets/certs/ klasörünün içine kopyalayın.
  3. Projenizin pubspec.yaml dosyasını açın ve assets bölümünü aşağıdaki gibi düzenleyin:
flutter:
uses-material-design: true

assets:
- assets/certs/my_certificate.crt

Adım 3: Kodu Yazmak

Şimdi sihrin gerçekleşeceği yere, yani koda geldik. Flutter’ın dahili http paketini kullanarak bu işlemi yapacağız.

Öncelikle pubspec.yaml dosyanıza http paketini eklediğinizden emin olun:
dependencies:
http: ^1.1.0 (veya en güncel sürüm)

Şimdi, ağ isteklerini yöneteceğimiz bir servis dosyası oluşturalım.

import 'dart:io';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';

class ApiService {

// Pinning yapılmış güvenli bir HTTP istemcisi (client) oluşturan metod.
Future<http.Client> _createPinnedClient() async {
// 1. Adım: Projeye eklediğimiz sertifika dosyasını okuyoruz.
final certData = await rootBundle.load('assets/certs/my_certificate.crt');

// 2. Adım: Güvenlik Bağlamı (SecurityContext) oluşturuyoruz.
// Bu, Flutter'a hangi sertifikalara güveneceğini söylediğimiz yer.
final securityContext = SecurityContext(withTrustedRoots: false);

// 3. Adım: Okuduğumuz sertifikayı "güvenilir" olarak tanıtıyoruz.
securityContext.setTrustedCertificatesBytes(certData.buffer.asUint8List());

// 4. Adım: Bu özel güvenlik kurallarıyla bir HttpClient oluşturuyoruz.
final httpClient = HttpClient(context: securityContext);

// 5. Adım: Standart http paketiyle uyumlu olması için IOClient'a dönüştürüyoruz.
return IOClient(httpClient);
}

// Veri çekmek için kullanacağımız ana metod.
Future<void> fetchData() async {
try {
// Güvenli istemcimizi oluşturuyoruz.
final client = await _createPinnedClient();

final response = await client.get(
Uri.parse('https://api.ornekuygulama.com/data'),
);

if (response.statusCode == 200) {
print('Bağlantı başarılı ve güvenli! Veri: ${response.body}');
} else {
print('Sunucu hatası: ${response.statusCode}');
}
} catch (e) {
// Eğer sertifika eşleşmezse, buraya bir hata düşecek!
print('Bağlantı hatası! Sertifika sabitleme başarısız olmuş olabilir: $e');
}
}
}
  • _createPinnedClient: Bu fonksiyon, tüm ağır işi yapar. assets’ten sertifikamızı yükler, sadece bu sertifikaya güveneceğini belirten bir SecurityContext oluşturur ve bu kurallara uyan özel bir http.Client döner. withTrustedRoots: false parametresi, telefondaki diğer güvenilir sertifikaları görmezden gelmesini sağlar. Bu çok önemlidir!
  • fetchData: Bu metod, normalde http.get() yapacağımız yerdir. Tek fark, isteği standart http yerine bizim oluşturduğumuz client üzerinden yapmasıdır. Eğer sunucudan gelen sertifika, bizim sabitlediğimizle eşleşmezse, catch bloğuna düşer ve bağlantı kurulmaz.

Değerli arkadaşlar bu yöntem güvenlik katmanınızı daha sağlam bir hale getirecektir. Elbette yapılabilecek tek şey bu değil ancak yöntemlerden biri olan bu konuyu sizlere aktarmak istedim.

Ayrıca sadece http paketi değil dio gibi popüler paketlerde de bu işlemi yapmak gayet mümkün. Dio üzerinde de bir örnek gösterip konuyu bitirelim.

import 'dart:io';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter/services.dart';

class ApiService {
late final Dio _dio;

ApiService() {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.ornekuygulama.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
));

// Burası sihrin gerçekleştiği yer!
// Dio'nun kullandığı HttpClient'ı özelleştiriyoruz.
(_dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
// Daha önce anlattığımız güvenli HttpClient'ı oluşturan kodun aynısı.
final client = HttpClient();

// Güvenlik bağlamını (SecurityContext) oluşturuyoruz.
// withTrustedRoots: false -> Cihazın kendi güvendiği sertifikaları yok say.
final securityContext = SecurityContext(withTrustedRoots: false);

// Asenkron bir işlem olduğu için bu kısmı doğrudan burada yapamıyoruz.
// Bu nedenle, güvenli client'ı ayrı bir asenkron fonksiyonda oluşturup
// Dio'ya atamak daha doğru bir yaklaşımdır.
// Gelin bu kısmı daha temiz bir hale getirelim.

// YANLIŞ YAKLAŞIM: createHttpClient senkron olduğu için burada await kullanamayız.
// final certData = await rootBundle.load('assets/certs/my_certificate.crt');
// securityContext.setTrustedCertificatesBytes(certData.buffer.asUint8List());

// client.context = securityContext; -> Bu şekilde doğrudan set edilemez.

// DOĞRU YAKLAŞIM AŞAĞIDADIR.
return client; // Bu satırı şimdilik bırakalım ve doğru yönteme bakalım.
};
}

// YUKARIDAKİ YÖNTEMİN DAHA TEMİZ VE DOĞRU HALİ
// ApiService'i asenkron bir fabrika (factory) metoduyla oluşturalım.

// Private constructor
ApiService._(this._dio);

// Asenkron fabrika metodu
static Future<ApiService> create() async {
// 1. Güvenli HttpClient'ı oluşturalım.
final securityContext = SecurityContext(withTrustedRoots: false);
final certData = await rootBundle.load('assets/certs/my_certificate.crt');
securityContext.setTrustedCertificatesBytes(certData.buffer.asUint8List());

final httpClient = HttpClient(context: securityContext);

// 2. Dio'yu bu güvenli client ile yapılandıralım.
final dio = Dio(BaseOptions(
baseUrl: 'https://api.ornekuygulama.com',
));

dio.httpClientAdapter = IOHttpClientAdapter(
createHttpClient: () => httpClient,
);

// 3. Yapılandırılmış Dio ile ApiService'i döndürelim.
return ApiService._(dio);
}

Future<void> fetchDataWithDio() async {
try {
final response = await _dio.get('/data');
if (response.statusCode == 200) {
print('Dio ile bağlantı başarılı ve güvenli! Veri: ${response.data}');
}
} on DioException catch (e) {
// Sertifika eşleşmezse veya başka bir ağ hatası olursa buraya düşer.
// Pinning hatası genellikle 'HandshakeException' içerir.
if (e.error is HandshakeException) {
print('Sertifika sabitleme hatası! Bağlantı reddedildi. Hata: ${e.message}');
} else {
print('Dio hatası: ${e.message}');
}
}
}
}

// KULLANIM ÖRNEĞİ
// Bir widget içinde bu servisi şu şekilde kullanabilirsiniz:
/*
late ApiService _apiService;
bool _isLoading = true;

@override
void initState() {
super.initState();
_initializeService();
}

void _initializeService() async {
_apiService = await ApiService.create();
setState(() {
_isLoading = false;
});
}
*/

Yukarıdaki kodda özellikle create adında bir static metot kullandığıma dikkat edin. Neden mi?

  1. Asenkron Başlatma: Sertifika dosyasını (assets’ten) okumak asenkron bir işlemdir (await rootBundle.load(…)). Ancak bir sınıfın kurucu metodu (constructor) async olamaz. Bu yüzden sınıfı oluşturma işini async olabilen bir static metoda (create) taşıdık.
  2. _createSecureHttpClient() Mantığı: Bu metodun içindeki mantık, bir önceki http paketi örneğindekiyle tamamen aynı. Sertifikayı yüklüyor ve sadece ona güvenen bir HttpClient nesnesi oluşturuyor.
  3. dio.httpClientAdapter = IOHttpClientAdapter(…): İşte kilit nokta burası. dio’nun adaptörünü yeni bir IOHttpClientAdapter ile değiştiriyoruz. Bu yeni adaptörün createHttpClient parametresine de bizim az önce oluşturduğumuz süper güvenli httpClient nesnesini veriyoruz.
  4. fetchDataWithDio(): Bu metod artık tamamen standart bir dio kullanımına benziyor. _dio.get() dediğimizde, dio arka planda bizim verdiğimiz güvenli HttpClient’ı kullanacak ve sertifika eşleşmezse isteği yapmadan bir DioException fırlatacaktır.

Dio üzerinde bu şekilde bir örnek oluşturdum. Dio paketi http paketine kıyasla çok daha geniş bir paket olduğu için özellik bakımından http paketi üzerinden anlatmak yeni başlayan birine de deneyimli birine de anlatımı kolaylaştırdığı için tercih sebebim oldu…

Unutmadan eklemek isterim bu yöntemde;
Sunucunuzdaki SSL sertifikası yenilendiğinde veya değiştiğinde, uygulamanız artık o sunucuya bağlanamaz! Çünkü uygulamanın içindeki eski sertifika ile sunucudaki yeni sertifika eşleşmez. Bu durumda, yeni sertifikayı uygulamaya ekleyip, kullanıcıların uygulamayı güncellemesini sağlamanız gerekir. Bu, özellikle finans veya bankacılık gibi yüksek güvenlik gerektiren uygulamalar için planlanması gereken bir operasyonel maliyettir.

Değerli arkadaşlar bir makalenin daha sonuna geldik umarım ilgilisi ve meraklısı için faydalı olmuştur.

Sonraki makalelerde görüşmek üzere…

Github: www.github.com/abdullah017
Linkedin: www.linkedin.com/in/abdullahtas
Stackoverflow:
https://stackoverflow.com/users/13807726/abdullah-t

--

--

AbdullahTaş
AbdullahTaş

Written by AbdullahTaş

FLUTTER DEVELOPER AT IWWOMI | HASURA-GRAPHQL & FIREBASE |

No responses yet