Bảo mật bằng giao thức mạng

Các hoạt động tương tác được mã hoá theo máy khách sử dụng giao thức Bảo mật tầng truyền tải (TLS) để bảo vệ dữ liệu của ứng dụng.

Bài viết này thảo luận những phương pháp hay nhất liên quan đến các phương pháp hay nhất về giao thức mạng bảo mật và những điểm cần cân nhắc về Cơ sở hạ tầng khoá công khai (PKI). Hãy đọc bài viết Tổng quan về tính bảo mật của Android cũng như bài viết Tổng quan về quyền để biết thêm thông tin chi tiết.

Khái niệm

Máy chủ có chứng chỉ TLS sẽ có một khoá công khai và một khoá riêng tư trùng khớp. Máy chủ này sử dụng quy trình mã hoá khoá công khai để ký chứng chỉ trong quá trình bắt tay TLS.

Một cái bắt tay đơn giản chỉ chứng minh rằng máy chủ biết khoá riêng tư của chứng chỉ. Để giải quyết trường hợp này, hãy cho phép ứng dụng tin cậy nhiều chứng chỉ. Một máy chủ cụ thể sẽ không đáng tin cậy nếu chứng chỉ của máy chủ đó không xuất hiện trong nhóm chứng chỉ đáng tin cậy phía máy khách.

Tuy nhiên, các máy chủ có thể sử dụng tính năng xoay vòng khoá để thay đổi khoá công khai của chứng chỉ bằng một khoá mới. Khi thay đổi cấu hình máy chủ, bạn cần phải cập nhật ứng dụng. Nếu máy chủ là dịch vụ web của bên thứ ba, chẳng hạn như một trình duyệt web hoặc ứng dụng email, thì sẽ khó biết được thời điểm cập nhật ứng dụng.

Các máy chủ thường dựa vào chứng chỉ của Tổ chức phát hành chứng chỉ (CA) để phát hành chứng chỉ, giúp cấu hình phía máy khách ổn định hơn theo thời gian. CA chứng chỉ máy chủ bằng cách sử dụng khoá riêng tư của chứng chỉ đó. Tiếp đến, ứng dụng có thể kiểm tra để đảm bảo máy chủ có chứng chỉ CA mà nền tảng đã biết.

Các CA đáng tin cậy thường được liệt kê trên nền tảng lưu trữ. Android 8.0 (API cấp 26) bao gồm hơn 100 CA được cập nhật trong mỗi phiên bản và không thay đổi giữa các thiết bị.

Các ứng dụng cần có một cơ chế xác minh máy chủ vì CA cung cấp chứng chỉ cho nhiều máy chủ. Chứng chỉ của CA xác định máy chủ bằng cách dùng một tên cụ thể, chẳng hạn như gmail.com hoặc dùng một ký tự đại diện, chẳng hạn như *.google.com.

Để xem thông tin chứng chỉ máy chủ của một trang web, hãy dùng lệnh s_client của công cụ openssl, giúp truyền dữ liệu vào số cổng. Theo mặc định, HTTPS sử dụng cổng 443.

Lệnh này sẽ truyền dữ liệu đầu ra openssl s_client sang openssl x509 để định dạng thông tin chứng chỉ theo tiêu chuẩn X.509. Lệnh này yêu cầu chủ đề (tên máy chủ) và nhà phát hành (CA).

openssl s_client -connect WEBSITE-URL:443 | \
  openssl x509 -noout -subject -issuer

Ví dụ về HTTPS

Giả sử bạn có một máy chủ web có chứng chỉ do một CA nổi tiếng phát hành, bạn có thể thực hiện một yêu cầu bảo mật như minh hoạ trong mã sau:

Kotlin

val url = URL("https://wikipedia.org")
val urlConnection: URLConnection = url.openConnection()
val inputStream: InputStream = urlConnection.getInputStream()
copyInputStreamToOutputStream(inputStream, System.out)

Java

URL url = new URL("https://wikipedia.org");
URLConnection urlConnection = url.openConnection();
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);

Để tuỳ chỉnh các yêu cầu HTTP, hãy truyền tới HttpURLConnection. Tài liệu HttpURLConnection của Android có các ví dụ minh hoạ cách xử lý tiêu đề phản hồi và yêu cầu, cách xuất bản nội dung, quản lý cookie, sử dụng proxy, lưu phản hồi vào bộ nhớ đệm và nhiều ví dụ khác. Khung Android xác minh các chứng chỉ và tên máy chủ bằng những API này.

Hãy sử dụng các API này bất cứ khi nào có thể. Phần sau đây trình bày những vấn đề thường gặp và cần có nhiều giải pháp.

Các vấn đề thường gặp khi xác minh chứng chỉ máy chủ

Giả sử rằng thay vì trả về nội dung, getInputStream() lại gửi một ngoại lệ:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
        at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
        at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
        at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
        at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
        at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
        at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
        at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)

Điều này có thể xảy ra vì một vài lý do như sau:

  1. Không xác định được CA phát hành chứng chỉ máy chủ.
  2. Chứng chỉ máy chủ không phải do một CA ký mà là tự ký.
  3. Cấu hình máy chủ thiếu một CA trung gian.

Các phần sau đây thảo luận cách giải quyết những vấn đề này trong khi vẫn đảm bảo an toàn cho kết nối của bạn đến máy chủ.

Tổ chức phát hành chứng chỉ không xác định

SSLHandshakeException phát sinh vì hệ thống không tin cậy CA. Điều này có thể là do bạn có chứng chỉ của một CA mới mà Android không tin tưởng hoặc do ứng dụng của bạn đang hoạt động trên một phiên bản cũ không có CA này. Vì mang tính riêng tư nên CA hiếm khi được xác định. Thường thì lý do không xác định được một CA là vì CA đó không phải là CA công khai mà là CA riêng tư do một tổ chức (như chính phủ, công ty hoặc tổ chức giáo dục) phát hành nhằm phục vụ cho mục đích sử dụng riêng.

Để tin cậy các CA tuỳ chỉnh mà không cần thay đổi mã của ứng dụng, hãy thay đổi Cấu hình bảo mật mạng.

Thận trọng: Nhiều trang web mô tả một giải pháp thay thế kém hiệu quả, đó là cài đặt một TrustManager mà không có tác dụng gì cả. Việc này khiến người dùng của bạn dễ bị tấn công khi sử dụng một điểm phát sóng Wi-Fi công cộng, vì kẻ tấn công có thể dùng thủ thuật DNS để gửi lưu lượng truy cập của người dùng qua một proxy giả mạo là máy chủ của bạn. Sau đó, kẻ tấn công có thể ghi lại mật khẩu và các dữ liệu cá nhân khác. Cách này hiệu quả vì kẻ tấn công có thể tạo một chứng chỉ và nếu không có một TrustManager xác thực rằng chứng chỉ đến từ một nguồn đáng tin cậy, thì bạn không thể chặn loại hình tấn công này. Vì vậy, đừng sử dụng giải pháp này, dù là tạm thời. Thay vào đó, hãy khiến ứng dụng tin tưởng nhà phát hành chứng chỉ của máy chủ.

Chứng chỉ máy chủ tự ký

Thứ hai, SSLHandshakeException có thể xảy ra do một chứng chỉ tự ký, khiến máy chủ tạo CA của riêng máy chủ đó. Điều này tương tự như một tổ chức phát hành chứng chỉ không xác định, vì vậy, hãy sửa đổi Cấu hình bảo mật mạng của ứng dụng để tin tưởng các chứng chỉ tự ký của bạn.

Thiếu tổ chức phát hành chứng chỉ trung gian

Thứ ba, SSLHandshakeException xảy ra do thiếu CA trung gian. CA công khai hiếm khi ký chứng chỉ máy chủ. Thay vào đó, CA gốc này sẽ ký các CA trung gian.

Để giảm rủi ro bị xâm phạm, các CA sẽ giữ CA gốc ở chế độ ngoại tuyến. Tuy nhiên, các hệ điều hành như Android thường chỉ trực tiếp tin tưởng các CA gốc, tạo ra một khoảng trống tin cậy nhỏ giữa chứng chỉ máy chủ (do CA trung gian ký) và trình xác minh chứng chỉ (giúp nhận dạng CA gốc).

Để xoá khoảng trống tin cậy này, máy chủ gửi một chuỗi chứng chỉ từ CA máy chủ, thông qua bất kỳ tổ chức trung gian nào, tới một CA gốc đáng tin cậy trong quá trình bắt tay TLS.

Ví dụ: sau đây là chuỗi chứng chỉ mail.google.com mà bạn có thể xem bằng lệnh openssls_client:

$ openssl s_client -connect mail.google.com:443
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=mail.google.com
   i:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
 1 s:/C=ZA/O=Thawte Consulting (Pty) Ltd./CN=Thawte SGC CA
   i:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
---

Ví dụ này cho thấy máy chủ gửi một chứng chỉ cho mail.google.com do CA Thawte SGC (một CA trung gian) phát hành và một chứng chỉ thứ hai cho CA Thawte SGC do CA Verisign (CA chính được Android tin cậy) phát hành.

Tuy nhiên, một máy chủ có thể không được định cấu hình để bao gồm CA trung gian cần thiết. Ví dụ: dưới đây là một máy chủ có thể gây ra lỗi trong trình duyệt Android và các trường hợp ngoại lệ trong ứng dụng Android:

$ openssl s_client -connect egov.uscis.gov:443
---
Certificate chain
 0 s:/C=US/ST=District Of Columbia/L=Washington/O=U.S. Department of Homeland Security/OU=United States Citizenship and Immigration Services/OU=Terms of use at www.verisign.com/rpa (c)05/CN=egov.uscis.gov
   i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 International Server CA - G3
---

Không giống như chứng chỉ máy chủ tự ký hoặc CA không xác định, hầu hết các trình duyệt dành cho máy tính đều không gặp lỗi khi giao tiếp với máy chủ này. Trình duyệt dành cho máy tính sẽ lưu các CA trung gian đáng tin cậy vào bộ nhớ đệm. Sau khi tìm hiểu về CA trung gian qua một trang web, trình duyệt sẽ không cần CA trung gian đó trong chuỗi chứng chỉ nữa.

Một số trang web chủ ý thực hiện việc này cho các máy chủ web phụ có chức năng phân phát tài nguyên. Để tiết kiệm băng thông, chúng có thể phân phát trang HTML chính của mình từ một máy chủ có chuỗi chứng chỉ đầy đủ, ngoại trừ hình ảnh, CSS và JavaScript, mà không cần CA. Thật không may, đôi khi các máy chủ này có thể cung cấp một dịch vụ web mà bạn đang cố gắng truy cập từ ứng dụng Android của mình và dịch vụ này không đủ khả năng chấp nhận.

Để khắc phục vấn đề này, hãy định cấu hình máy chủ để đưa CA trung gian vào chuỗi máy chủ. Hầu hết CA đều cung cấp hướng dẫn về cách thực hiện việc này đối với các máy chủ web phổ biến.

Cảnh báo về việc sử dụng trực tiếp SSLSocket

Cho đến nay, các ví dụ đều tập trung vào HTTPS bằng cách sử dụng HttpsURLConnection. Đôi khi, các ứng dụng cần sử dụng TLS tách biệt với HTTPS. Ví dụ: một ứng dụng email có thể sử dụng các biến thể TLS của SMTP, POP3 hoặc IMAP. Trong những trường hợp đó, ứng dụng có thể sử dụng trực tiếp SSLSocket, giống như cách HttpsURLConnection thực hiện trong nội bộ.

Cho đến nay, các kỹ thuật được mô tả để xử lý những vấn đề liên quan đến việc xác minh chứng chỉ cũng áp dụng cho SSLSocket. Trên thực tế, khi sử dụng một TrustManager tuỳ chỉnh, nội dung được truyền đến HttpsURLConnectionSSLSocketFactory. Vì vậy, nếu bạn cần sử dụng một TrustManager tuỳ chỉnh có SSLSocket, hãy làm theo các bước tương tự và dùng SSLSocketFactory đó để tạo SSLSocket.

Thận trọng:SSLSocket không thực hiện quy trình xác minh tên máy chủ. Ứng dụng của bạn có thể tuỳ ý xác minh tên máy chủ, tốt nhất là gọi getDefaultHostnameVerifier() bằng tên máy chủ dự kiến. Ngoài ra, hãy lưu ý rằng HostnameVerifier.verify() không gửi trường hợp ngoại lệ về lỗi. Thay vào đó, phương thức này sẽ trả về một kết quả boolean mà bạn phải kiểm tra một cách rõ ràng.

Các CA bị chặn

TLS dựa vào các CA để chỉ phát hành chứng chỉ cho những chủ sở hữu đã xác minh của máy chủ và miền. Trong một số ít trường hợp, các CA bị lừa hoặc, trong trường hợp chứng chỉ Comodo hay DigiNotar, bị xâm phạm, dẫn đến việc chứng chỉ cho một tên máy chủ được phát hành cho một người nào đó không phải chủ sở hữu của máy chủ hoặc miền.

Để giảm thiểu rủi ro này, Android có thể thêm một số chứng chỉ hoặc thậm chí toàn bộ các CA vào một danh sách từ chối. Mặc dù trước đây danh sách này đã được tích hợp vào hệ điều hành, nhưng kể từ Android 4.2, danh sách này có thể được cập nhật từ xa để xử lý những trường hợp xâm phạm trong tương lai.

Giới hạn ứng dụng của bạn cho các chứng chỉ cụ thể

Thận trọng: Bạn không nên dùng tính năng ghim chứng chỉ (phương pháp hạn chế mà theo đó, các chứng chỉ được xem là hợp lệ cho ứng dụng của bạn nếu bạn đã từng cho phép các chứng chỉ đó) đối với các ứng dụng Android. Các thay đổi về cấu hình máy chủ trong tương lai, chẳng hạn như thay đổi sang một CA khác, hiển thị ứng dụng có các chứng chỉ đã ghim, sẽ không thể kết nối với máy chủ nếu không nhận được bản cập nhật phần mềm ứng dụng.

Nếu muốn chỉ cho phép ứng dụng chấp nhận các chứng chỉ mà bạn chỉ định, bạn cần phải thêm nhiều ghim dự phòng, bao gồm ít nhất một khoá hoàn toàn thuộc quyền kiểm soát của bạn và thời gian hết hạn đủ ngắn để phòng tránh các vấn đề về khả năng tương thích. Cấu hình bảo mật mạng cung cấp tính năng ghim kèm những chức năng này.

Chứng chỉ ứng dụng

Bài viết này tập trung vào việc sử dụng TLS để bảo mật hoạt động giao tiếp với máy chủ. TLS cũng hỗ trợ khái niệm chứng chỉ ứng dụng, cho phép máy chủ xác thực danh tính của một ứng dụng. Mặc dù nằm ngoài phạm vi của bài viết này, nhưng các kỹ thuật có liên quan cũng tương tự như việc chỉ định một TrustManager tuỳ chỉnh.

Nogotofail: Một công cụ kiểm thử tính bảo mật của lưu lượng truy cập mạng

Nogotofail là một công cụ giúp bạn dễ dàng xác nhận rằng ứng dụng của mình an toàn trước các cấu hình sai và lỗ hổng bảo mật TLS/SSL đã biết. Đây là một công cụ tự động, mạnh mẽ và mở rộng được, dùng để kiểm thử các vấn đề bảo mật mạng trên mọi thiết bị có lưu lượng truy cập mạng có thể được đặt để đi qua công cụ này.

Nogotofail hữu ích trong 3 trường hợp sử dụng chính sau đây:

  • Tìm lỗi và lỗ hổng bảo mật.
  • Xác minh kết quả khắc phục và theo dõi số lần hồi quy.
  • Nắm bắt các ứng dụng và thiết bị đang tạo ra lưu lượng truy cập.

Nogotofail hoạt động trên Android, iOS, Linux, Windows, ChromeOS, macOS và thực tế là mọi thiết bị mà bạn sử dụng để kết nối Internet. Đây là một ứng dụng có sẵn để định cấu hình chế độ cài đặt và nhận thông báo trên Android và Linux, đồng thời chính bản thân công cụ tấn công này có thể được triển khai làm bộ định tuyến, máy chủ VPN hoặc proxy.

Bạn có thể truy cập vào công cụ này tại dự án nguồn mở Nogotofail.

Các điểm cập nhật đối với SSL và TLS

Android 10

Một số trình duyệt, chẳng hạn như Google Chrome, cho phép người dùng chọn chứng chỉ khi máy chủ TLS gửi một thông báo yêu cầu chứng chỉ trong quá trình bắt tay TLS. Kể từ Android 10, các đối tượng KeyChain sẽ tôn trọng nhà phát hành và các thông số kỹ thuật chính khi gọi KeyChain.choosePrivateKeyAlias() để cho người dùng thấy lời nhắc lựa chọn chứng chỉ. Cụ thể, lời nhắc này không chứa các lựa chọn không tuân thủ thông số kỹ thuật của máy chủ.

Nếu không có chứng chỉ nào mà người dùng có thể chọn, chẳng hạn như khi không có chứng chỉ nào khớp với thông số kỹ thuật của máy chủ hoặc thiết bị không cài đặt bất kỳ chứng chỉ nào, thì lời nhắc chọn chứng chỉ sẽ không xuất hiện.

Ngoài ra, trên Android 10 trở lên, bạn không nhất thiết phải có phương thức khoá màn hình thiết bị để nhập các khoá hoặc chứng chỉ CA vào đối tượng KeyChain.

TLS 1.3 được bật theo mặc định

Trên Android 10 trở lên, TLS 1.3 được bật theo mặc định cho mọi kết nối TLS. Dưới đây là một số thông tin quan trọng về hoạt động triển khai TLS 1.3 của chúng tôi:

  • Bộ thuật toán mật mã TLS 1.3 không tuỳ chỉnh được. Bộ thuật toán mật mã TLS 1.3 được hỗ trợ luôn bật khi TLS 1.3 được bật. Mọi nỗ lực tắt bộ thuật toán này bằng cách gọi setEnabledCipherSuites() đều sẽ bị bỏ qua.
  • Khi thương lượng TLS 1.3, các đối tượng HandshakeCompletedListener sẽ được gọi trước khi các phiên được thêm vào bộ nhớ đệm của phiên. (Trong TLS 1.2 và các phiên bản khác trước đó, các đối tượng này được gọi sau khi phiên được thêm vào bộ nhớ đệm của phiên.)
  • Trong một số trường hợp trên các phiên bản trước đó của Android, các thực thể SSLEngine sẽ gửi một SSLHandshakeException, thì trên Android 10 trở lên, các thực thể này sẽ gửi một SSLProtocolException.
  • Không hỗ trợ chế độ 0-RTT.

Nếu muốn, bạn có thể lấy một SSLContext đã vô hiệu hoá TLS 1.3 bằng cách gọi SSLContext.getInstance("TLSv1.2"). Bạn cũng có thể kích hoạt hoặc vô hiệu hoá các phiên bản giao thức trên cơ sở từng kết nối bằng cách gọi setEnabledProtocols() trên một đối tượng thích hợp.

Các chứng chỉ được ký bằng SHA-1 không đáng tin cậy trong TLS

Trên Android 10, các chứng chỉ sử dụng thuật toán hàm băm SHA-1 sẽ không được tin cậy trong các kết nối TLS. Các CA gốc không phát hành những chứng chỉ như vậy kể từ năm 2016 và chúng không còn đáng tin cậy trong Chrome hoặc các trình duyệt lớn khác.

Mọi nỗ lực kết nối đều không thành công nếu kết nối đến một trang web đưa ra chứng chỉ sử dụng SHA-1.

Các thay đổi và điểm cải thiện trong hành vi của KeyChain

Một số trình duyệt, chẳng hạn như Google Chrome, cho phép người dùng chọn một chứng chỉ khi máy chủ TLS gửi thông báo yêu cầu chứng chỉ trong quá trình bắt tay TLS. Kể từ Android 10, các đối tượng KeyChain sẽ tôn trọng nhà phát hành và các thông số kỹ thuật chính khi gọi KeyChain.choosePrivateKeyAlias() để cho người dùng thấy lời nhắc lựa chọn chứng chỉ. Cụ thể, lời nhắc này không chứa các lựa chọn không tuân thủ thông số kỹ thuật của máy chủ.

Nếu không có chứng chỉ nào mà người dùng có thể chọn, chẳng hạn như khi không có chứng chỉ nào khớp với thông số kỹ thuật của máy chủ hoặc thiết bị không cài đặt bất kỳ chứng chỉ nào, thì lời nhắc chọn chứng chỉ sẽ không xuất hiện.

Ngoài ra, trên Android 10 trở lên, bạn không nhất thiết phải có phương thức khoá màn hình thiết bị để nhập các khoá hoặc chứng chỉ CA vào đối tượng KeyChain.

Những thay đổi khác về TLS và mật mã học

Dưới đây là một số thay đổi nhỏ trong thư viện TLS và mật mã học có hiệu lực trên Android 10:

  • Các thuật toán mật mã AES/GCM/NoPadding và ChaCha20/Poly1305/NoPadding sẽ trả về dung lượng bộ nhớ đệm chính xác hơn từ getOutputSize().
  • Bộ thuật toán mật mã TLS_FALLBACK_SCSV bị loại bỏ khỏi các nỗ lực kết nối bằng một giao thức max TLS 1.2 trở lên. Do hoạt động triển khai máy chủ TLS đã được cải thiện, bạn không nên thử phương thức dự phòng TLS bên ngoài. Thay vào đó, bạn nên dựa vào quá trình thương lượng phiên bản TLS.
  • ChaCha20-Poly1305 là bí danh của ChaCha20/Poly1305/NoPadding.
  • Tên máy chủ có dấu chấm theo sau không được xem là tên máy chủ SNI hợp lệ.
  • Phần mở rộng supported_signature_algorithms trong CertificateRequest được tuân thủ khi chọn khoá ký cho các phản hồi chứng chỉ.
  • Bạn có thể dùng các khoá ký mờ (chẳng hạn như các khoá trong Kho khoá Android) với chữ ký RSA-PSS trong TLS.

Các thay đổi về kết nối HTTPS

Nếu một ứng dụng chạy Android 10 truyền giá trị rỗng vào setSSLSocketFactory(), thì IllegalArgumentException sẽ xảy ra. Trong các phiên bản trước, việc truyền giá trị rỗng vào setSSLSocketFactory() có cùng kết quả như khi truyền vào factory mặc định hiện tại.

Android 11

Cổng SSL dùng công cụ SSL Conscrypt theo mặc định

Hoạt động triển khai SSLSocket mặc định của Android dựa trên Conscrypt. Kể từ Android 11, hoạt động triển khai đó được xây dựng nội bộ dựa trên SSLEngine của Conscrypt.