Commit 789f5a95 authored by Emily Stark's avatar Emily Stark Committed by Commit Bot

Add CT compliance status to DevTools security panel

This shows the CT compliance status in the Certificate Transparency section of
the Security Panel origin details view. Showing the SCTs themselves isn't
sufficient because a site could have e.g. not enough SCTs or SCTs from an
insufficent set of logs, and thus might not be CT-compliance even though it has
SCTs.

I also added a message about when a resource was loaded from cache to the origin
details view. This is useful beacuse not all security details are stored in the
cache, so it can explain why some security details (including SCTs and CT
compliance status) can be missing in this view sometimes.

Bug: 695610
Change-Id: Ib9c8b0af5a08dbc5eefcf778fc030c045622505e
Reviewed-on: https://chromium-review.googlesource.com/998445
Commit-Queue: Emily Stark <estark@chromium.org>
Reviewed-by: default avatarPavel Feldman <pfeldman@chromium.org>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#548677}
parent 4ec864a1
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
#include "net/base/net_errors.h" #include "net/base/net_errors.h"
#include "net/base/upload_bytes_element_reader.h" #include "net/base/upload_bytes_element_reader.h"
#include "net/cert/ct_policy_status.h"
#include "net/cert/ct_sct_to_string.h" #include "net/cert/ct_sct_to_string.h"
#include "net/cookies/canonical_cookie.h" #include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_store.h" #include "net/cookies/cookie_store.h"
...@@ -79,6 +80,26 @@ using ClearBrowserCookiesCallback = ...@@ -79,6 +80,26 @@ using ClearBrowserCookiesCallback =
const char kDevToolsEmulateNetworkConditionsClientId[] = const char kDevToolsEmulateNetworkConditionsClientId[] =
"X-DevTools-Emulate-Network-Conditions-Client-Id"; "X-DevTools-Emulate-Network-Conditions-Client-Id";
Network::CertificateTransparencyCompliance SerializeCTPolicyCompliance(
net::ct::CTPolicyCompliance ct_compliance) {
switch (ct_compliance) {
case net::ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS:
return Network::CertificateTransparencyComplianceEnum::Compliant;
case net::ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS:
case net::ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS:
return Network::CertificateTransparencyComplianceEnum::NotCompliant;
case net::ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY:
case net::ct::CTPolicyCompliance::
CT_POLICY_COMPLIANCE_DETAILS_NOT_AVAILABLE:
return Network::CertificateTransparencyComplianceEnum::Unknown;
case net::ct::CTPolicyCompliance::CT_POLICY_MAX:
NOTREACHED();
return Network::CertificateTransparencyComplianceEnum::Unknown;
}
NOTREACHED();
return Network::CertificateTransparencyComplianceEnum::Unknown;
}
std::unique_ptr<Network::Cookie> BuildCookie( std::unique_ptr<Network::Cookie> BuildCookie(
const net::CanonicalCookie& cookie) { const net::CanonicalCookie& cookie) {
std::unique_ptr<Network::Cookie> devtools_cookie = std::unique_ptr<Network::Cookie> devtools_cookie =
...@@ -1302,6 +1323,8 @@ std::unique_ptr<protocol::Network::SecurityDetails> BuildSecurityDetails( ...@@ -1302,6 +1323,8 @@ std::unique_ptr<protocol::Network::SecurityDetails> BuildSecurityDetails(
.SetCertificateId(0) // Keep this in protocol for compatability. .SetCertificateId(0) // Keep this in protocol for compatability.
.SetSignedCertificateTimestampList( .SetSignedCertificateTimestampList(
std::move(signed_certificate_timestamp_list)) std::move(signed_certificate_timestamp_list))
.SetCertificateTransparencyCompliance(
SerializeCTPolicyCompliance(ssl_info.ct_policy_compliance))
.Build(); .Build();
if (ssl_info.key_exchange_group != 0) { if (ssl_info.key_exchange_group != 0) {
...@@ -1312,6 +1335,7 @@ std::unique_ptr<protocol::Network::SecurityDetails> BuildSecurityDetails( ...@@ -1312,6 +1335,7 @@ std::unique_ptr<protocol::Network::SecurityDetails> BuildSecurityDetails(
} }
if (mac) if (mac)
security_details->SetMac(mac); security_details->SetMac(mac);
return security_details; return security_details;
} }
......
Tests that the panel includes Certificate Transparency compliance status
Panel on origin view:
<DIV class=widget vbox security-origin-view insertion-point-main >
<STYLE type=text/css >
</STYLE>
<STYLE type=text/css >
</STYLE>
<DIV class=title-section >
<DIV class=title-section-header >
Origin
</DIV>
<DIV class=origin-display >
<SPAN class=security-property security-property-secure >
</SPAN>
<SPAN is=url-text >
<SPAN class=url-scheme-secure >
https
</SPAN>
<SPAN class=url-scheme-separator >
://
</SPAN>
<SPAN >
foo.test
</SPAN>
</SPAN>
</DIV>
<DIV class=view-network-button >
<BUTTON is=text-button type=button class=origin-button >
View requests in Network Panel
<#document-fragment >
<STYLE type=text/css >
</STYLE>
<STYLE type=text/css >
</STYLE>
<STYLE type=text/css >
</STYLE>
<CONTENT >
</CONTENT>
</#document-fragment>
</BUTTON>
</DIV>
</DIV>
<DIV class=origin-view-section >
<DIV class=origin-view-section-title >
Connection
</DIV>
<TABLE class=details-table >
<DIV class=details-table-row >
<DIV >
Protocol
</DIV>
<DIV >
TLS 1.2
</DIV>
</DIV>
<DIV class=details-table-row >
<DIV >
Key exchange
</DIV>
<DIV >
Key_Exchange
</DIV>
</DIV>
<DIV class=details-table-row >
<DIV >
Cipher
</DIV>
<DIV >
Cypher with Mac
</DIV>
</DIV>
</TABLE>
</DIV>
<DIV class=origin-view-section >
<DIV class=origin-view-section-title >
Certificate
</DIV>
<TABLE class=details-table >
<DIV class=details-table-row >
<DIV >
Subject
</DIV>
<DIV >
foo.test
</DIV>
</DIV>
<DIV class=details-table-row >
<DIV >
SAN
</DIV>
<DIV >
<DIV >
<SPAN class=san-entry >
foo.test
</SPAN>
<SPAN class=san-entry >
*.test
</SPAN>
</DIV>
</DIV>
</DIV>
<DIV class=details-table-row >
<DIV >
Valid from
</DIV>
<DIV >
Mon, 20 Mar 2017 08:53:20 GMT
</DIV>
</DIV>
<DIV class=details-table-row >
<DIV >
Valid until
</DIV>
<DIV >
Wed, 18 May 2033 03:33:20 GMT
</DIV>
</DIV>
<DIV class=details-table-row >
<DIV >
Issuer
</DIV>
<DIV >
Super CA
</DIV>
</DIV>
<DIV class=details-table-row >
<DIV >
</DIV>
<DIV >
<BUTTON is=text-button type=button class=origin-button >
Open full certificate details
<#document-fragment >
<STYLE type=text/css >
</STYLE>
<STYLE type=text/css >
</STYLE>
<STYLE type=text/css >
</STYLE>
<CONTENT >
</CONTENT>
</#document-fragment>
</BUTTON>
</DIV>
</DIV>
</TABLE>
</DIV>
<DIV class=origin-view-section >
<DIV class=origin-view-section-title >
Certificate Transparency
</DIV>
<TABLE class=details-table sct-summary >
</TABLE>
<DIV class=sct-details hidden >
</DIV>
<DIV class=origin-view-section-notes >
This request complies with Chrome's Certificate Transparency policy.
</DIV>
</DIV>
<DIV class=origin-view-section origin-view-notes >
<DIV >
The security details above are from the first inspected response.
</DIV>
</DIV>
</DIV>
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(async function() {
TestRunner.addResult(
`Tests that the panel includes Certificate Transparency compliance status\n`);
await TestRunner.loadModule('security_test_runner');
await TestRunner.showPanel('security');
var request1 = new SDK.NetworkRequest(0, 'https://foo.test/', 'https://foo.test', 0, 0, null);
request1.setSecurityState(Protocol.Security.SecurityState.Secure);
let securityDetails = {};
securityDetails.protocol = 'TLS 1.2';
securityDetails.keyExchange = 'Key_Exchange';
securityDetails.keyExchangeGroup = '';
securityDetails.cipher = 'Cypher';
securityDetails.mac = 'Mac';
securityDetails.subjectName = 'foo.test';
securityDetails.sanList = ['foo.test', '*.test'];
securityDetails.issuer = 'Super CA';
securityDetails.validFrom = 1490000000;
securityDetails.validTo = 2000000000;
securityDetails.CertificateId = 0;
securityDetails.signedCertificateTimestampList = [];
securityDetails.certificateTransparencyCompliance = Protocol.Network.CertificateTransparencyCompliance.Compliant;
request1.setSecurityDetails(securityDetails);
SecurityTestRunner.dispatchRequestFinished(request1);
Security.SecurityPanel._instance()._sidebarTree._elementsByOrigin.get('https://foo.test').select();
TestRunner.addResult('Panel on origin view:');
TestRunner.dumpDeepInnerHTML(Security.SecurityPanel._instance()._visibleView.contentElement);
TestRunner.completeTest();
})();
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
securityDetails.validTo = 2000000000; securityDetails.validTo = 2000000000;
securityDetails.CertificateId = 0; securityDetails.CertificateId = 0;
securityDetails.signedCertificateTimestampList = []; securityDetails.signedCertificateTimestampList = [];
securityDetails.certificateTransparencyCompliance = Protocol.Network.CertificateTransparencyCompliance.Unknown;
request3.setSecurityDetails(securityDetails); request3.setSecurityDetails(securityDetails);
SecurityTestRunner.dispatchRequestFinished(request3); SecurityTestRunner.dispatchRequestFinished(request3);
......
...@@ -149,6 +149,23 @@ bool LoadsFromCacheOnly(const ResourceRequest& request) { ...@@ -149,6 +149,23 @@ bool LoadsFromCacheOnly(const ResourceRequest& request) {
return false; return false;
} }
protocol::Network::CertificateTransparencyCompliance
SerializeCTPolicyCompliance(
ResourceResponse::CTPolicyCompliance ct_compliance) {
switch (ct_compliance) {
case ResourceResponse::kCTPolicyComplianceDetailsNotAvailable:
return protocol::Network::CertificateTransparencyComplianceEnum::Unknown;
case ResourceResponse::kCTPolicyComplies:
return protocol::Network::CertificateTransparencyComplianceEnum::
Compliant;
case ResourceResponse::kCTPolicyDoesNotComply:
return protocol::Network::CertificateTransparencyComplianceEnum::
NotCompliant;
}
NOTREACHED();
return protocol::Network::CertificateTransparencyComplianceEnum::Unknown;
}
static std::unique_ptr<protocol::Network::Headers> BuildObjectForHeaders( static std::unique_ptr<protocol::Network::Headers> BuildObjectForHeaders(
const HTTPHeaderMap& headers) { const HTTPHeaderMap& headers) {
std::unique_ptr<protocol::DictionaryValue> headers_object = std::unique_ptr<protocol::DictionaryValue> headers_object =
...@@ -644,6 +661,8 @@ BuildObjectForResourceResponse(const ResourceResponse& response, ...@@ -644,6 +661,8 @@ BuildObjectForResourceResponse(const ResourceResponse& response,
.setCertificateId(0) // Keep this in protocol for compatability. .setCertificateId(0) // Keep this in protocol for compatability.
.setSignedCertificateTimestampList( .setSignedCertificateTimestampList(
std::move(signed_certificate_timestamp_list)) std::move(signed_certificate_timestamp_list))
.setCertificateTransparencyCompliance(
SerializeCTPolicyCompliance(response.GetCTPolicyCompliance()))
.build(); .build();
if (response_security_details->key_exchange_group.length() > 0) if (response_security_details->key_exchange_group.length() > 0)
security_details->setKeyExchangeGroup( security_details->setKeyExchangeGroup(
......
...@@ -7508,9 +7508,24 @@ ...@@ -7508,9 +7508,24 @@
"items": { "items": {
"$ref": "SignedCertificateTimestamp" "$ref": "SignedCertificateTimestamp"
} }
},
{
"name": "certificateTransparencyCompliance",
"description": "Whether the request complied with Certificate Transparency policy",
"$ref": "CertificateTransparencyCompliance"
} }
] ]
}, },
{
"id": "CertificateTransparencyCompliance",
"description": "Whether the request complied with Certificate Transparency policy.",
"type": "string",
"enum": [
"unknown",
"not-compliant",
"compliant"
]
},
{ {
"id": "BlockedReason", "id": "BlockedReason",
"description": "The reason why request was blocked.", "description": "The reason why request was blocked.",
......
...@@ -3435,6 +3435,15 @@ domain Network ...@@ -3435,6 +3435,15 @@ domain Network
TimeSinceEpoch validTo TimeSinceEpoch validTo
# List of signed certificate timestamps (SCTs). # List of signed certificate timestamps (SCTs).
array of SignedCertificateTimestamp signedCertificateTimestampList array of SignedCertificateTimestamp signedCertificateTimestampList
# Whether the request complied with Certificate Transparency policy
CertificateTransparencyCompliance certificateTransparencyCompliance
# Whether the request complied with Certificate Transparency policy.
type CertificateTransparencyCompliance extends string
enum
unknown
not-compliant
compliant
# The reason why request was blocked. # The reason why request was blocked.
type BlockedReason extends string type BlockedReason extends string
......
...@@ -229,6 +229,7 @@ Security.SecurityPanel = class extends UI.PanelWithSidebar { ...@@ -229,6 +229,7 @@ Security.SecurityPanel = class extends UI.PanelWithSidebar {
if (securityDetails) if (securityDetails)
originState.securityDetails = securityDetails; originState.securityDetails = securityDetails;
originState.loadedFromCache = request.cached();
this._origins.set(origin, originState); this._origins.set(origin, originState);
...@@ -372,10 +373,11 @@ Security.SecurityPanel.Origin; ...@@ -372,10 +373,11 @@ Security.SecurityPanel.Origin;
/** /**
* @typedef {Object} * @typedef {Object}
* @property {!Protocol.Security.SecurityState} securityState - Current security state of the origin. * @property {!Protocol.Security.SecurityState} securityState
* @property {?Protocol.Network.SecurityDetails} securityDetails - Security details of the origin, if available. * @property {?Protocol.Network.SecurityDetails} securityDetails
* @property {?Promise<>} certificateDetailsPromise - Certificate details of the origin. * @property {?Promise<>} certificateDetailsPromise
* @property {?Security.SecurityOriginView} originView - Current SecurityOriginView corresponding to origin. * @property {?bool} loadedFromCache
* @property {?Security.SecurityOriginView} originView
*/ */
Security.SecurityPanel.OriginState; Security.SecurityPanel.OriginState;
...@@ -819,8 +821,10 @@ Security.SecurityOriginView = class extends UI.VBox { ...@@ -819,8 +821,10 @@ Security.SecurityOriginView = class extends UI.VBox {
const certificateSection = this.element.createChild('div', 'origin-view-section'); const certificateSection = this.element.createChild('div', 'origin-view-section');
certificateSection.createChild('div', 'origin-view-section-title').textContent = Common.UIString('Certificate'); certificateSection.createChild('div', 'origin-view-section-title').textContent = Common.UIString('Certificate');
const sctListLength = originState.securityDetails.signedCertificateTimestampList.length;
const ctCompliance = originState.securityDetails.certificateTransparencyCompliance;
let sctSection; let sctSection;
if (originState.securityDetails.signedCertificateTimestampList.length) { if (sctListLength || ctCompliance !== Protocol.Network.CertificateTransparencyCompliance.Unknown) {
// Create the Certificate Transparency section outside the callback, so that it appears in the right place. // Create the Certificate Transparency section outside the callback, so that it appears in the right place.
sctSection = this.element.createChild('div', 'origin-view-section'); sctSection = this.element.createChild('div', 'origin-view-section');
sctSection.createChild('div', 'origin-view-section-title').textContent = sctSection.createChild('div', 'origin-view-section-title').textContent =
...@@ -851,7 +855,7 @@ Security.SecurityOriginView = class extends UI.VBox { ...@@ -851,7 +855,7 @@ Security.SecurityOriginView = class extends UI.VBox {
const sctSummaryTable = new Security.SecurityDetailsTable(); const sctSummaryTable = new Security.SecurityDetailsTable();
sctSummaryTable.element().classList.add('sct-summary'); sctSummaryTable.element().classList.add('sct-summary');
sctSection.appendChild(sctSummaryTable.element()); sctSection.appendChild(sctSummaryTable.element());
for (let i = 0; i < originState.securityDetails.signedCertificateTimestampList.length; i++) { for (let i = 0; i < sctListLength; i++) {
const sct = originState.securityDetails.signedCertificateTimestampList[i]; const sct = originState.securityDetails.signedCertificateTimestampList[i];
sctSummaryTable.addRow( sctSummaryTable.addRow(
Common.UIString('SCT'), sct.logDescription + ' (' + sct.origin + ', ' + sct.status + ')'); Common.UIString('SCT'), sct.logDescription + ' (' + sct.origin + ', ' + sct.status + ')');
...@@ -860,7 +864,7 @@ Security.SecurityOriginView = class extends UI.VBox { ...@@ -860,7 +864,7 @@ Security.SecurityOriginView = class extends UI.VBox {
// Show detailed SCT(s) of Certificate Transparency. // Show detailed SCT(s) of Certificate Transparency.
const sctTableWrapper = sctSection.createChild('div', 'sct-details'); const sctTableWrapper = sctSection.createChild('div', 'sct-details');
sctTableWrapper.classList.add('hidden'); sctTableWrapper.classList.add('hidden');
for (let i = 0; i < originState.securityDetails.signedCertificateTimestampList.length; i++) { for (let i = 0; i < sctListLength; i++) {
const sctTable = new Security.SecurityDetailsTable(); const sctTable = new Security.SecurityDetailsTable();
sctTableWrapper.appendChild(sctTable.element()); sctTableWrapper.appendChild(sctTable.element());
const sct = originState.securityDetails.signedCertificateTimestampList[i]; const sct = originState.securityDetails.signedCertificateTimestampList[i];
...@@ -875,21 +879,40 @@ Security.SecurityOriginView = class extends UI.VBox { ...@@ -875,21 +879,40 @@ Security.SecurityOriginView = class extends UI.VBox {
} }
// Add link to toggle between displaying of the summary of the SCT(s) and the detailed SCT(s). // Add link to toggle between displaying of the summary of the SCT(s) and the detailed SCT(s).
const toggleSctsDetailsLink = sctSection.createChild('div', 'link'); if (sctListLength) {
toggleSctsDetailsLink.classList.add('sct-toggle'); const toggleSctsDetailsLink = sctSection.createChild('div', 'link');
toggleSctsDetailsLink.textContent = Common.UIString('Show full details'); toggleSctsDetailsLink.classList.add('sct-toggle');
function toggleSctDetailsDisplay() { toggleSctsDetailsLink.textContent = Common.UIString('Show full details');
const isDetailsShown = !sctTableWrapper.classList.contains('hidden'); function toggleSctDetailsDisplay() {
if (isDetailsShown) const isDetailsShown = !sctTableWrapper.classList.contains('hidden');
toggleSctsDetailsLink.textContent = Common.UIString('Show full details'); if (isDetailsShown)
else toggleSctsDetailsLink.textContent = Common.UIString('Show full details');
toggleSctsDetailsLink.textContent = Common.UIString('Hide full details'); else
sctSummaryTable.element().classList.toggle('hidden'); toggleSctsDetailsLink.textContent = Common.UIString('Hide full details');
sctTableWrapper.classList.toggle('hidden'); sctSummaryTable.element().classList.toggle('hidden');
sctTableWrapper.classList.toggle('hidden');
}
toggleSctsDetailsLink.addEventListener('click', toggleSctDetailsDisplay, false);
}
switch (ctCompliance) {
case Protocol.Network.CertificateTransparencyCompliance.Compliant:
sctSection.createChild('div', 'origin-view-section-notes').textContent =
Common.UIString('This request complies with Chrome\'s Certificate Transparency policy.');
break;
case Protocol.Network.CertificateTransparencyCompliance.NotCompliant:
sctSection.createChild('div', 'origin-view-section-notes').textContent =
Common.UIString('This request does not comply with Chrome\'s Certificate Transparency policy.');
break;
case Protocol.Network.CertificateTransparencyCompliance.Unknown:
break;
} }
toggleSctsDetailsLink.addEventListener('click', toggleSctDetailsDisplay, false);
const noteSection = this.element.createChild('div', 'origin-view-section origin-view-notes'); const noteSection = this.element.createChild('div', 'origin-view-section origin-view-notes');
if (originState.loadedFromCache) {
noteSection.createChild('div').textContent =
Common.UIString('This response was loaded from cache. Some security details might be missing.');
}
noteSection.createChild('div').textContent = noteSection.createChild('div').textContent =
Common.UIString('The security details above are from the first inspected response.'); Common.UIString('The security details above are from the first inspected response.');
} else if (originState.securityState !== Protocol.Security.SecurityState.Unknown) { } else if (originState.securityState !== Protocol.Security.SecurityState.Unknown) {
......
...@@ -32,6 +32,11 @@ ...@@ -32,6 +32,11 @@
color: #8c8c8c; color: #8c8c8c;
} }
.origin-view-section-notes {
margin-top: 6px;
color: #8c8c8c;
}
.security-origin-view .origin-display { .security-origin-view .origin-display {
font-size: 12px; font-size: 12px;
padding-left: 38px; padding-left: 38px;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment