← All reports

Implement 2-QWAC fetching

Component: WebKit NetworkProcess | 3dfdbed

Source/WebKit/NetworkProcess/QualifiedServerTrustFetch.cpp

+void QualifiedServerTrustFetch::didFinishLoading(const NetworkLoadMetrics&)
+{
+ WebCore::CertificateInfo qualifiedServerTrust;
+ if (m_debugEnabledForTesting) {
+ // FIXME: Once implementation of SecQWACTLSBindingVerify is available, ...
+ qualifiedServerTrust = m_serverTrust;
+ }
+#if PLATFORM(COCOA)
+ else if (canLoad_Security_SecQWACTLSBindingVerify()) {
+ SecTrustRef trust { nullptr };
+ bool success = softLink_Security_SecQWACTLSBindingVerify(m_buffer.takeBuffer()->makeContiguous()->createCFData().get(), m_serverTrust.trust(), &trust, nullptr);
+ if (trust)
+ qualifiedServerTrust = WebCore::CertificateInfo(adoptCF(trust));
+ }
+#endif
+ ...
+}

Source/WebKit/NetworkProcess/NetworkResourceLoader.cpp

+void NetworkResourceLoader::checkForQualifiedServerTrust(const WebCore::ResourceResponse& response)
+{
+ ...
+ if (!response.url().protocolIs("https"_s))
+ return;
+ ...
+ for (const auto& header : headerSet) {
+ if (!WebCore::LinkRelAttribute(nullptr, header.rel()).isTLSCertificateBinding)
+ continue;
+ URL url { header.url() };
+ if (!url.isValid())
+ continue;
+ if (protocolHostAndPortAreEqual(response.url(), url))
+ return QualifiedServerTrustFetch::create(*session, url, m_parameters, *tlsCertificates);
+ }
+}

2-QWAC은 사이트의 TLS 인증서를 별도로 발급되는 "qualified" 인증서와 out-of-band로 결합하려는 제안 사양(ETSI TS 119 411-5)입니다. 표준 CA 발급 인증서보다 강화된 신뢰 신호를 제공하는 것이 목적입니다. WebKit의 프로세스 모델에서는 network process가 raw network I/O와 TLS trust evaluation을 전담하고, UI process가 WKWebView 대상 API와 페이지별 상태를 관리합니다.

이번 변경으로 WebKit은 main resource response에 담긴 tls-certificate-binding Link header를 파싱하게 되었습니다. 이어서 network process 내부에 새로 추가된 QualifiedServerTrustFetch 객체를 통해 해당 URL을 same-origin으로 fetch하고, 그 결과를 evaluate합니다. 마지막으로 새로 추가된 NetworkProcessProxy::ReceivedQualifiedServerTrust IPC 메시지를 통해 UI process로 결과가 전달되며, 이는 KVO로 관찰 가능한 WKWebView.qualifiedServerTrust property로 노출됩니다. LinkRelAttribute 파싱 로직은 Document가 null이어도 동작하도록 일반화되었는데, network process에서는 header를 검사하는 시점에 Document 자체가 존재하지 않기 때문입니다. 결과적으로 기존에는 document에 종속적이던 API가 document context 없이도 호출될 수 있는 형태로 바뀐 셈입니다.

Web page load                         Network Process                        UI Process
  │                                         │                                      │
  ├─ main resource response ───────────────►│                                      │
  │   Link: <url>; rel=tls-certificate-      ├─ parse Link header                  │
  │         binding                          ├─ QualifiedServerTrustFetch::create  │
  │                                          │     (same-origin check, then         │
  │                                          │      m_networkLoad->start())          │
  │                                          │     fetch(url) ────► server          │
  │                                          │◄──── response ───────               │
  │                                          ├─ SecQWACTLSBindingVerify (or         │
  │                                          │   debug-only TLS-trust reuse)        │
  │                                          ├─ IPC: receivedQualifiedServerTrust ─►│
  │                                          │                                      ├─ WebPageProxy::receivedQualifiedServerTrust
  │                                          │                                      ├─ PageLoadState::receivedQualifiedServerTrust
  │                                          │                                      └─ WKWebView.qualifiedServerTrust (KVO)

서버가 제공하는 response header에 의해 트리거되는, 프로세스 간에 걸친 완전히 새로운 trust 확립 기능입니다. 실제 binding validation은 사용 가능하지 않을 수도 있는 soft-linked private SPI에 의존합니다. SecQWACTLSBindingVerify를 로드할 수 없는 경우, fetch는 그냥 빈 trust 결과를 만들어냅니다. 오직 테스트 전용 debug flag가 명시적으로 켜져 있을 때만 기존 TLS trust를 대신 재사용합니다.

앞으로 눈여겨봐야 할 패턴은 페이지 단위로 in-flight load를 keyed하여 유지하는 keep-alive map 구조입니다. 두 번째 요청이 진행 중이던 요청을 대체할 때, cancel과 completion 중 어느 쪽이 먼저 처리되느냐에 따라 옛 객체의 completion 경로가 여전히 실행될지 여부가 갈립니다. 좁게 보면, QualifiedServerTrustFetch 안의 전역 keep-alive map이 바로 이 사례에 해당합니다. Static이나 process-global 컨테이너가 load 객체에 대한 Ref를 들고 있으면서 동시에 completion 시점에 스스로 제거되는 구조라면 주의가 필요합니다. 이런 구조에서는 대체 삽입(replacement insert)과 completion에 의한 제거(completion removal)가 같은 key를 두고 race하면서 엉뚱한 entry가 지워질 가능성이 있기 때문입니다. 조금 더 넓게 보면, 동일한 keep-alive-until-completion idiom을 쓰는 다른 network-process 객체들도 점검 대상입니다. PingLoad, beacon 및 preconnect 경로, 그리고 NetworkResourceLoader의 completion handler들이 in-flight 상태에서 교체되는 동일한 구조를 갖는지 확인해야 합니다. 아울러 이들 각각의 buffering cap도 함께 점검할 필요가 있습니다(이번 케이스는 didReceiveBuffer에서 10MB로 제한됨). Cap이 없으면 unbounded accumulation 문제로 이어질 수 있습니다. 가장 넓게 보면, LinkRelAttribute에서 새로 nullable해진 Document 경로가 이식 가능한 교훈을 제공합니다. "항상 context 객체를 가진다"는 전제로 설계된 API가 "context 없이 호출될 수도 있다"는 형태로 일반화되면, 기존 caller들이 암묵적으로 전제하던 non-null assumption이 검증되지 않은 채 남게 됩니다. Source/WebCore 안에서 Document*를 인자로 받던 다른 parser들 중 null-tolerant overload가 추가된 사례를 검색해 보고, 각 함수 body에 새 call site가 도달할 수 있는 dereference가 남아 있는지 확인할 필요가 있습니다.