Skip to content

Commit cd581af

Browse files
authored
Handle certificate corruption (#7982)
1 parent 708d89b commit cd581af

File tree

2 files changed

+45
-1
lines changed

2 files changed

+45
-1
lines changed

okhttp/src/main/kotlin/okhttp3/Cache.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,8 @@ class Cache internal constructor(
609609
for (i in 0 until length) {
610610
val line = source.readUtf8LineStrict()
611611
val bytes = Buffer()
612-
bytes.write(line.decodeBase64()!!)
612+
val certificateBytes = line.decodeBase64() ?: throw IOException("Corrupt certificate in cache entry")
613+
bytes.write(certificateBytes)
613614
result.add(certificateFactory.generateCertificate(bytes.inputStream()))
614615
}
615616
return result

okhttp/src/test/java/okhttp3/CacheTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,49 @@ private void testResponseCaching(TransferKind transferKind) throws IOException {
296296
assertThat(response2.handshake().localPrincipal()).isEqualTo(localPrincipal);
297297
}
298298

299+
@Test public void secureResponseCachingWithCorruption() throws IOException {
300+
server.useHttps(handshakeCertificates.sslSocketFactory());
301+
server.enqueue(new MockResponse.Builder()
302+
.addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))
303+
.addHeader("Expires: " + formatDate(1, TimeUnit.HOURS))
304+
.body("ABC")
305+
.build());
306+
server.enqueue(new MockResponse.Builder()
307+
.addHeader("Last-Modified: " + formatDate(-5, TimeUnit.MINUTES))
308+
.addHeader("Expires: " + formatDate(2, TimeUnit.HOURS))
309+
.body("DEF")
310+
.build());
311+
312+
client = client.newBuilder()
313+
.sslSocketFactory(
314+
handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager())
315+
.hostnameVerifier(NULL_HOSTNAME_VERIFIER)
316+
.build();
317+
318+
Request request = new Request.Builder().url(server.url("/")).build();
319+
Response response1 = client.newCall(request).execute();
320+
assertThat(response1.body().string()).isEqualTo("ABC");
321+
322+
Path cacheEntry = fileSystem.allPaths().stream()
323+
.filter((e) -> e.name().endsWith(".0"))
324+
.findFirst()
325+
.orElseThrow();
326+
corruptCertificate(cacheEntry);
327+
328+
Response response2 = client.newCall(request).execute(); // Not Cached!
329+
assertThat(response2.body().string()).isEqualTo("DEF");
330+
331+
assertThat(cache.requestCount()).isEqualTo(2);
332+
assertThat(cache.networkCount()).isEqualTo(2);
333+
assertThat(cache.hitCount()).isEqualTo(0);
334+
}
335+
336+
private void corruptCertificate(Path cacheEntry) throws IOException {
337+
String content = Okio.buffer(fileSystem.source(cacheEntry)).readUtf8();
338+
content = content.replace("MII", "!!!");
339+
Okio.buffer(fileSystem.sink(cacheEntry)).writeUtf8(content).close();
340+
}
341+
299342
@Test public void responseCachingAndRedirects() throws Exception {
300343
server.enqueue(new MockResponse()
301344
.addHeader("Last-Modified: " + formatDate(-1, TimeUnit.HOURS))

0 commit comments

Comments
 (0)