Chuyện Gì Đang Xảy Ra
Ứng dụng Android của bạn ném ra lỗi này khi thực hiện request HTTPS:
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found
Stack SSL của Android không thể xác minh chuỗi certificate của server với bất kỳ Certificate Authority (CA) tin cậy nào trong trust store của nó. Có ba nguyên nhân thường gặp:
- Server dùng self-signed certificate — phổ biến ở môi trường dev và staging
- Certificate được ký bởi CA nội bộ hoặc private CA mà Android chưa từng biết đến
- Chuỗi certificate không đầy đủ — server chỉ gửi leaf cert, không gửi kèm các intermediate cert
Debug Trước Đã
Chưa cần đụng vào code. Hãy xác định xem bạn đang gặp trường hợp nào trong ba trường hợp trên.
Kiểm tra chuỗi certificate
Chạy lệnh này từ terminal — thay bằng hostname thực của bạn:
openssl s_client -connect your-api.example.com:443 -showcerts
Tìm các dòng sau trong output:
verify error:num=19:self signed certificate in certificate chain→ self-signed certverify error:num=20:unable to get local issuer certificate→ thiếu intermediate cert- Chuỗi xác minh tốt trên desktop nhưng thất bại trên Android → root CA đó được thêm vào trust store của Android sau phiên bản OS mà thiết bị đang chạy
Kiểm tra trên các phiên bản Android khác nhau
System CA store của Android không cố định — nó được cập nhật theo mỗi bản phát hành OS. Cert hoạt động tốt trên Android 9 (API 28) có thể thất bại im lặng trên Android 7 (API 24). Nếu lỗi chỉ xuất hiện trên thiết bị cũ, rất có thể bạn đang gặp phải root CA được thêm vào sau phiên bản đó. Kiểm tra repository CA certificate của Android để xác nhận thời điểm một root cụ thể được thêm vào.
Giải Pháp 1: Bundle Certificate (Self-Signed hoặc Private CA)
Dành cho server dev/staging hoặc API nội bộ dùng private CA — đây là cách fix đúng, không phải workaround.
Bước 1: Export certificate
openssl s_client -connect your-api.example.com:443 -showcerts 2>/dev/null | \
openssl x509 -outform PEM > mycert.pem
Đặt mycert.pem vào app/src/main/res/raw/mycert.pem.
Bước 2: Tạo Network Security Config
Tạo file app/src/main/res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">your-api.example.com</domain>
<trust-anchors>
<certificates src="@raw/mycert"/>
<certificates src="system"/>
</trust-anchors>
</domain-config>
</network-security-config>
Dòng <certificates src="system"/> rất quan trọng — nó giữ cho HTTPS thông thường hoạt động với mọi domain khác. Nếu thiếu dòng này, ứng dụng sẽ từ chối toàn bộ các cert được hệ thống tin cậy.
Bước 3: Khai báo trong AndroidManifest.xml
<application
android:networkSecurityConfig="@xml/network_security_config"
...>
Vậy là xong. Không cần thay đổi OkHttp. Android tự xử lý việc xác minh trust, và cấu hình được ghi rõ ràng trong một file XML duy nhất.
Giải Pháp 2: Sửa Thiếu Intermediate Certificate (Server Production)
Production đột nhiên bắt đầu ném lỗi này? Server có thể không gửi đầy đủ chuỗi certificate. Chín trong mười trường hợp, đây là lỗi cấu hình phía server — không phải vấn đề của Android.
Xác nhận lại:
openssl s_client -connect your-api.example.com:443 -verify_return_error
Sau đó thêm intermediate cert vào cấu hình web server:
Nginx:
# Ghép leaf cert + intermediate CA thành một file duy nhất
cat your_cert.pem intermediate.pem > fullchain.pem
# Trong nginx.conf:
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/private.key;
Apache:
SSLCertificateFile /path/to/your_cert.pem
SSLCertificateChainFile /path/to/intermediate.pem
Sau khi reload server, chạy lại openssl s_client và xác nhận độ sâu chuỗi là 2 hoặc 3 — không phải 1.
Giải Pháp 3: OkHttp Custom Trust Manager (Kiểm Soát Ở Tầng Code)
Network Security Config xử lý được hầu hết các trường hợp. Nhưng nếu bạn cần load certificate động lúc runtime — chẳng hạn lấy từ backend — bạn cần khai báo trực tiếp trong OkHttp:
// Kotlin
fun buildOkHttpClient(context: Context): OkHttpClient {
val cf = CertificateFactory.getInstance("X.509")
val cert = context.resources.openRawResource(R.raw.mycert).use {
cf.generateCertificate(it) as X509Certificate
}
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null, null)
setCertificateEntry("ca", cert)
}
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
init(keyStore)
}
val sslContext = SSLContext.getInstance("TLS").apply {
init(null, tmf.trustManagers, null)
}
return OkHttpClient.Builder()
.sslSocketFactory(sslContext.socketFactory, tmf.trustManagers[0] as X509TrustManager)
.build()
}
Kết nối vào Retrofit:
val retrofit = Retrofit.Builder()
.baseUrl("https://your-api.example.com/")
.client(buildOkHttpClient(context))
.addConverterFactory(GsonConverterFactory.create())
.build()
Những Điều Không Nên Làm
Stack Overflow tràn ngập pattern này. Hãy tránh hoàn toàn:
// ĐỪNG LÀM THẾ NÀY — vô hiệu hóa toàn bộ xác minh certificate
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
})
Đoạn code này vô hiệu hóa toàn bộ quá trình xác minh certificate. Bất kỳ kẻ tấn công nào trên cùng mạng đều có thể chặn traffic HTTPS của bạn. Bộ scanner tự động của Google Play Store sẽ gắn cờ pattern này — và những ứng dụng lọt qua vẫn có nguy cơ bị gỡ xuống sau khi đợt review bảo mật phát hiện ra.
Xác Nhận Fix Hoạt Động
- Clean build:
./gradlew clean assembleDebug - Cài lên thiết bị và trigger HTTPS call
- Kiểm tra Logcat —
SSLHandshakeExceptionphải biến mất - Chạy lại
openssl s_client— tìm dòngVerify return code: 0 (ok) - Test trên ít nhất hai thiết bị: một emulator mới (Android 10+) và một thiết bị vật lý cũ hơn (Android 6 hoặc 7) để phát hiện khoảng trống trong CA trust store
Mẹo: Xác Nhận Bạn Đã Export Đúng Certificate
Khi bundle cert từ staging server, nên kiểm tra lại để chắc chắn bạn lấy đúng cert. Tạo fingerprint SHA-256 và so sánh với cert mà server thực sự trình bày:
openssl x509 -in mycert.pem -fingerprint -sha256 -noout
Không có OpenSSL? Hash Generator của ToolCraft cho phép bạn dán nội dung certificate và lấy hash SHA-256 hoặc MD5 ngay trên trình duyệt — tiện lợi khi bạn đang dùng máy bị hạn chế phần mềm.
Những Điểm Cần Nhớ
- Không bao giờ bỏ qua xác minh SSL — dù chỉ "để test." Các hack tạm thời có thói quen len lỏi vào production.
- Network Security Config là công cụ mặc định của bạn. Nó có tính khai báo, giới hạn phạm vi cho từng domain cụ thể, và không làm rải rác logic trust ra khắp code networking.
- Thiếu intermediate cert gây ra nhiều sự cố production hơn self-signed cert. Luôn chạy
openssl s_clienttrước khi kết luận vấn đề nằm ở phía Android. - Giới hạn custom trust anchor cho các domain cụ thể — dùng block
<domain-config>, không phải override toàn cục.

