UNPKG

chrome-devtools-frontend

Version:
1,223 lines (1,099 loc) • 75.4 kB
// Copyright 2015 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. /* eslint-disable rulesdir/no_underscored_properties */ import * as Common from '../common/common.js'; import * as Host from '../host/host.js'; import * as i18n from '../i18n/i18n.js'; import * as Network from '../network/network.js'; import * as SDK from '../sdk/sdk.js'; import * as UI from '../ui/ui.js'; import {Events, PageSecurityState, PageVisibleSecurityState, SecurityModel, SecurityStyleExplanation, SummaryMessages} from './SecurityModel.js'; // eslint-disable-line no-unused-vars export const UIStrings = { /** *@description Title text content in Security Panel of the Security panel */ overview: 'Overview', /** *@description Text in Security Panel of the Security panel */ mainOrigin: 'Main origin', /** *@description Text in Security Panel of the Security panel */ nonsecureOrigins: 'Non-secure origins', /** *@description Text in Security Panel of the Security panel */ secureOrigins: 'Secure origins', /** *@description Text in Security Panel of the Security panel */ unknownCanceled: 'Unknown / canceled', /** *@description Text in Security Panel of the Security panel */ reloadToViewDetails: 'Reload to view details', /** *@description New parent title in Security Panel of the Security panel */ mainOriginSecure: 'Main origin (secure)', /** *@description New parent title in Security Panel of the Security panel */ mainOriginNonsecure: 'Main origin (non-secure)', /** *@description Summary div text content in Security Panel of the Security panel */ securityOverview: 'Security overview', /** *@description Text to show something is secure */ secure: 'Secure', /** *@description Sdk console message message level info of level Labels in Console View of the Console panel */ info: 'Info', /** *@description Not secure div text content in Security Panel of the Security panel */ notSecure: 'Not secure', /** *@description Text to view a security certificate */ viewCertificate: 'View certificate', /** *@description Text in Security Panel of the Security panel */ theSecurityOfThisPageIsUnknown: 'The security of this page is unknown.', /** *@description Text in Security Panel of the Security panel */ thisPageIsNotSecure: 'This page is not secure.', /** *@description Text in Security Panel of the Security panel */ thisPageIsSecureValidHttps: 'This page is secure (valid HTTPS).', /** *@description Text in Security Panel of the Security panel */ thisPageIsNotSecureBrokenHttps: 'This page is not secure (broken HTTPS).', /** *@description Text in Security Panel of the Security panel */ notSecureBroken: 'Not secure (broken)', /** *@description Main summary for page when it has been deemed unsafe by the SafeBrowsing service. */ thisPageIsDangerousFlaggedBy: 'This page is dangerous (flagged by Google Safe Browsing).', /** *@description Summary phrase for a security problem where the site is deemed unsafe by the SafeBrowsing service. */ flaggedByGoogleSafeBrowsing: 'Flagged by Google Safe Browsing', /** *@description Description of a security problem where the site is deemed unsafe by the SafeBrowsing service. */ toCheckThisPagesStatusVisit: 'To check this page\'s status, visit g.co/safebrowsingstatus.', /** *@description Main summary for a non cert error page. */ thisIsAnErrorPage: 'This is an error page.', /** *@description Main summary for where the site is non-secure HTTP. */ thisPageIsInsecureUnencrypted: 'This page is insecure (unencrypted HTTP).', /** *@description Summary phrase for a security problem where the site is non-secure (HTTP) and user has entered data in a form field. */ formFieldEditedOnANonsecurePage: 'Form field edited on a non-secure page', /** *@description Description of a security problem where the site is non-secure (HTTP) and user has entered data in a form field. */ dataWasEnteredInAFieldOnA: 'Data was entered in a field on a non-secure page. A warning has been added to the URL bar.', /** *@description Main summary for where the site has a non-cryptographic secure origin. */ thisPageHasANonhttpsSecureOrigin: 'This page has a non-HTTPS secure origin.', /** *@description Message to display in devtools security tab when the page you are on triggered a safety tip. */ thisPageIsSuspicious: 'This page is suspicious', /** *@description Body of message to display in devtools security tab when you are viewing a page that triggered a safety tip. */ chromeHasDeterminedThatThisSiteS: 'Chrome has determined that this site could be fake or fraudulent.', /** *@description Second part of the body of message to display in devtools security tab when you are viewing a page that triggered a safety tip. */ ifYouBelieveThisIsShownIn: 'If you believe this is shown in error please visit https://bugs.chromium.org/p/chromium/issues/entry?template=Safety+Tips+Appeals.', /** *@description Summary of a warning when the user visits a page that triggered a Safety Tip because the domain looked like another domain. */ possibleSpoofingUrl: 'Possible spoofing URL', /** *@description Body of a warning when the user visits a page that triggered a Safety Tip because the domain looked like another domain. *@example {wikipedia.org} PH1 */ thisSitesHostnameLooksSimilarToP: 'This site\'s hostname looks similar to {PH1}. Attackers sometimes mimic sites by making small, hard-to-see changes to the domain name.', /** *@description second part of body of a warning when the user visits a page that triggered a Safety Tip because the domain looked like another domain. */ ifYouBelieveThisIsShownInErrorSafety: 'If you believe this is shown in error please visit https://bugs.chromium.org/p/chromium/issues/entry?template=Safety+Tips+Appeals.', /** *@description Title of the devtools security tab when the page you are on triggered a safety tip. */ thisPageIsSuspiciousFlaggedBy: 'This page is suspicious (flagged by Chrome).', /** *@description Text for a security certificate */ certificate: 'Certificate', /** *@description Summary phrase for a security problem where the site's certificate chain contains a SHA1 signature. */ insecureSha: 'insecure (SHA-1)', /** *@description Description of a security problem where the site's certificate chain contains a SHA1 signature. */ theCertificateChainForThisSite: 'The certificate chain for this site contains a certificate signed using SHA-1.', /** *@description Summary phrase for a security problem where the site's certificate is missing a subjectAltName extension. */ subjectAlternativeNameMissing: 'Subject Alternative Name missing', /** *@description Description of a security problem where the site's certificate is missing a subjectAltName extension. */ theCertificateForThisSiteDoesNot: 'The certificate for this site does not contain a Subject Alternative Name extension containing a domain name or IP address.', /** *@description Summary phrase for a security problem with the site's certificate. */ missing: 'missing', /** *@description Description of a security problem with the site's certificate. *@example {net::ERR_CERT_AUTHORITY_INVALID} PH1 */ thisSiteIsMissingAValidTrusted: 'This site is missing a valid, trusted certificate ({PH1}).', /** *@description Summary phrase for a site that has a valid server certificate. */ validAndTrusted: 'valid and trusted', /** *@description Description of a site that has a valid server certificate. *@example {Let's Encrypt Authority X3} PH1 */ theConnectionToThisSiteIsUsingA: 'The connection to this site is using a valid, trusted server certificate issued by {PH1}.', /** *@description Summary phrase for a security state where Private Key Pinning is ignored because the certificate chains to a locally-trusted root. */ publickeypinningBypassed: 'Public-Key-Pinning bypassed', /** *@description Description of a security state where Private Key Pinning is ignored because the certificate chains to a locally-trusted root. */ publickeypinningWasBypassedByA: 'Public-Key-Pinning was bypassed by a local root certificate.', /** *@description Summary phrase for a site with a certificate that is expiring soon. */ certificateExpiresSoon: 'Certificate expires soon', /** *@description Description for a site with a certificate that is expiring soon. */ theCertificateForThisSiteExpires: 'The certificate for this site expires in less than 48 hours and needs to be renewed.', /** *@description Text that refers to the network connection */ connection: 'Connection', /** *@description Summary phrase for a site that uses a modern, secure TLS protocol and cipher. */ secureConnectionSettings: 'secure connection settings', /** *@description Description of a site's TLS settings. *@example {TLS 1.2} PH1 *@example {ECDHE_RSA} PH2 *@example {AES_128_GCM} PH3 */ theConnectionToThisSiteIs: 'The connection to this site is encrypted and authenticated using {PH1}, {PH2}, and {PH3}.', /** *@description A recommendation to the site owner to use a modern TLS protocol *@example {TLS 1.0} PH1 */ sIsObsoleteEnableTlsOrLater: '{PH1} is obsolete. Enable TLS 1.2 or later.', /** *@description A recommendation to the site owner to use a modern TLS key exchange */ rsaKeyExchangeIsObsoleteEnableAn: 'RSA key exchange is obsolete. Enable an ECDHE-based cipher suite.', /** *@description A recommendation to the site owner to use a modern TLS cipher *@example {3DES_EDE_CBC} PH1 */ sIsObsoleteEnableAnAesgcmbased: '{PH1} is obsolete. Enable an AES-GCM-based cipher suite.', /** *@description A recommendation to the site owner to use a modern TLS server signature */ theServerSignatureUsesShaWhichIs: 'The server signature uses SHA-1, which is obsolete. Enable a SHA-2 signature algorithm instead. (Note this is different from the signature in the certificate.)', /** *@description Summary phrase for a site that uses an outdated SSL settings (protocol, key exchange, or cipher). */ obsoleteConnectionSettings: 'obsolete connection settings', /** *@description A title of the 'Resources' action category */ resources: 'Resources', /** *@description Summary for page when there is active mixed content */ activeMixedContent: 'active mixed content', /** *@description Description for page when there is active mixed content */ youHaveRecentlyAllowedNonsecure: 'You have recently allowed non-secure content (such as scripts or iframes) to run on this site.', /** *@description Summary for page when there is mixed content */ mixedContent: 'mixed content', /** *@description Description for page when there is mixed content */ thisPageIncludesHttpResources: 'This page includes HTTP resources.', /** *@description Summary for page when there is a non-secure form */ nonsecureForm: 'non-secure form', /** *@description Description for page when there is a non-secure form */ thisPageIncludesAFormWithA: 'This page includes a form with a non-secure "action" attribute.', /** *@description Summary for the page when it contains active content with certificate error */ activeContentWithCertificate: 'active content with certificate errors', /** *@description Description for the page when it contains active content with certificate error */ youHaveRecentlyAllowedContent: 'You have recently allowed content loaded with certificate errors (such as scripts or iframes) to run on this site.', /** *@description Summary for page when there is active content with certificate errors */ contentWithCertificateErrors: 'content with certificate errors', /** *@description Description for page when there is content with certificate errors */ thisPageIncludesResourcesThat: 'This page includes resources that were loaded with certificate errors.', /** *@description Summary for page when all resources are served securely */ allServedSecurely: 'all served securely', /** *@description Description for page when all resources are served securely */ allResourcesOnThisPageAreServed: 'All resources on this page are served securely.', /** *@description Text in Security Panel of the Security panel */ blockedMixedContent: 'Blocked mixed content', /** *@description Text in Security Panel of the Security panel */ yourPageRequestedNonsecure: 'Your page requested non-secure resources that were blocked.', /** *@description Refresh prompt text content in Security Panel of the Security panel */ reloadThePageToRecordRequestsFor: 'Reload the page to record requests for HTTP resources.', /** *@description Requests anchor text content in Security Panel of the Security panel *@example {1} PH1 */ viewDRequestInNetworkPanel: 'View {PH1} request in Network Panel', /** *@description Requests anchor text content in Security Panel of the Security panel *@example {2} PH1 */ viewDRequestsInNetworkPanel: 'View {PH1} requests in Network Panel', /** *@description Text for the origin of something */ origin: 'Origin', /** *@description Text in Security Panel of the Security panel */ viewRequestsInNetworkPanel: 'View requests in Network Panel', /** *@description Text for security or network protocol */ protocol: 'Protocol', /** *@description Text in Security Panel of the Security panel */ keyExchange: 'Key exchange', /** *@description Text in Security Panel of the Security panel */ keyExchangeGroup: 'Key exchange group', /** *@description Text in Security Panel of the Security panel */ cipher: 'Cipher', /** *@description Sct div text content in Security Panel of the Security panel */ certificateTransparency: 'Certificate Transparency', /** *@description Text that refers to the subject of a security certificate */ subject: 'Subject', /** *@description Text in Security Panel of the Security panel */ san: 'SAN', /** *@description Text to show since when an item is valid */ validFrom: 'Valid from', /** *@description Text to indicate the expiry date */ validUntil: 'Valid until', /** *@description Text for the issuer of an item */ issuer: 'Issuer', /** *@description Text in Security Panel of the Security panel */ openFullCertificateDetails: 'Open full certificate details', /** *@description Text in Security Panel of the Security panel */ sct: 'SCT', /** *@description Text in Security Panel of the Security panel */ logName: 'Log name', /** *@description Text in Security Panel of the Security panel */ logId: 'Log ID', /** *@description Text in Security Panel of the Security panel */ validationStatus: 'Validation status', /** *@description Text for the source of something */ source: 'Source', /** * @description Label for a date/time string in the Security panel. It indicates the time at which * a security certificate was issued (created by an authority and distributed). */ issuedAt: 'Issued at', /** *@description Text in Security Panel of the Security panel */ hashAlgorithm: 'Hash algorithm', /** *@description Text in Security Panel of the Security panel */ signatureAlgorithm: 'Signature algorithm', /** *@description Text in Security Panel of the Security panel */ signatureData: 'Signature data', /** *@description Toggle scts details link text content in Security Panel of the Security panel */ showFullDetails: 'Show full details', /** *@description Toggle scts details link text content in Security Panel of the Security panel */ hideFullDetails: 'Hide full details', /** *@description Text in Security Panel of the Security panel */ thisRequestCompliesWithChromes: 'This request complies with `Chrome`\'s Certificate Transparency policy.', /** *@description Text in Security Panel of the Security panel */ thisRequestDoesNotComplyWith: 'This request does not comply with `Chrome`\'s Certificate Transparency policy.', /** *@description Text in Security Panel of the Security panel */ thisResponseWasLoadedFromCache: 'This response was loaded from cache. Some security details might be missing.', /** *@description Text in Security Panel of the Security panel */ theSecurityDetailsAboveAreFrom: 'The security details above are from the first inspected response.', /** *@description Main summary for where the site has a non-cryptographic secure origin. */ thisOriginIsANonhttpsSecure: 'This origin is a non-HTTPS secure origin.', /** *@description Text in Security Panel of the Security panel */ yourConnectionToThisOriginIsNot: 'Your connection to this origin is not secure.', /** *@description No info div text content in Security Panel of the Security panel */ noSecurityInformation: 'No security information', /** *@description Text in Security Panel of the Security panel */ noSecurityDetailsAreAvailableFor: 'No security details are available for this origin.', /** *@description San div text content in Security Panel of the Security panel */ na: '(n/a)', /** *@description Text to show less content */ showLess: 'Show less', /** *@description Truncated santoggle text content in Security Panel of the Security panel *@example {2} PH1 */ showMoreSTotal: 'Show more ({PH1} total)', }; const str_ = i18n.i18n.registerUIStrings('security/SecurityPanel.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); let securityPanelInstance: SecurityPanel; export class SecurityPanel extends UI.Panel.PanelWithSidebar implements SDK.SDKModel.SDKModelObserver<SecurityModel> { _mainView: SecurityMainView; _sidebarMainViewElement: SecurityPanelSidebarTreeElement; _sidebarTree: SecurityPanelSidebarTree; _lastResponseReceivedForLoaderId: Map<string, SDK.NetworkRequest.NetworkRequest>; _origins: Map<string, OriginState>; _filterRequestCounts: Map<string, number>; _visibleView: UI.Widget.VBox|null; _eventListeners: Common.EventTarget.EventDescriptor[]; _securityModel: SecurityModel|null; private constructor() { super('security'); this._mainView = new SecurityMainView(this); const title = document.createElement('span'); title.classList.add('title'); title.textContent = i18nString(UIStrings.overview); this._sidebarMainViewElement = new SecurityPanelSidebarTreeElement( title, this._setVisibleView.bind(this, this._mainView), 'security-main-view-sidebar-tree-item', 'lock-icon'); this._sidebarMainViewElement.tooltip = title.textContent; this._sidebarTree = new SecurityPanelSidebarTree(this._sidebarMainViewElement, this.showOrigin.bind(this)); this.panelSidebarElement().appendChild(this._sidebarTree.element); this._lastResponseReceivedForLoaderId = new Map(); this._origins = new Map(); this._filterRequestCounts = new Map(); SDK.SDKModel.TargetManager.instance().observeModels(SecurityModel, this); this._visibleView = null; this._eventListeners = []; this._securityModel = null; } static instance(opts: {forceNew: boolean|null} = {forceNew: null}): SecurityPanel { const {forceNew} = opts; if (!securityPanelInstance || forceNew) { securityPanelInstance = new SecurityPanel(); } return securityPanelInstance; } static _instance(): SecurityPanel { return SecurityPanel.instance(); } static createCertificateViewerButtonForOrigin(text: string, origin: string): Element { const certificateButton = UI.UIUtils.createTextButton(text, async (e: Event) => { e.consume(); const names = await SDK.NetworkManager.MultitargetNetworkManager.instance().getCertificate(origin); if (names.length > 0) { Host.InspectorFrontendHost.InspectorFrontendHostInstance.showCertificateViewer(names); } }, 'origin-button'); UI.ARIAUtils.markAsButton(certificateButton); return certificateButton; } static createCertificateViewerButtonForCert(text: string, names: string[]): Element { const certificateButton = UI.UIUtils.createTextButton(text, e => { e.consume(); Host.InspectorFrontendHost.InspectorFrontendHostInstance.showCertificateViewer(names); }, 'origin-button'); UI.ARIAUtils.markAsButton(certificateButton); return certificateButton; } static createHighlightedUrl(url: string, securityState: string): Element { const schemeSeparator = '://'; const index = url.indexOf(schemeSeparator); // If the separator is not found, just display the text without highlighting. if (index === -1) { const text = document.createElement('span'); text.textContent = url; return text; } const highlightedUrl = document.createElement('span'); const scheme = url.substr(0, index); const content = url.substr(index + schemeSeparator.length); highlightedUrl.createChild('span', 'url-scheme-' + securityState).textContent = scheme; highlightedUrl.createChild('span', 'url-scheme-separator').textContent = schemeSeparator; highlightedUrl.createChild('span').textContent = content; return highlightedUrl; } _updateSecurityState( newSecurityState: Protocol.Security.SecurityState, explanations: Protocol.Security.SecurityStateExplanation[], summary: string|null): void { this._sidebarMainViewElement.setSecurityState(newSecurityState); this._mainView.updateSecurityState(newSecurityState, explanations, summary); } _onSecurityStateChanged(event: Common.EventTarget.EventTargetEvent): void { const data = event.data as PageSecurityState; const securityState = data.securityState as Protocol.Security.SecurityState; const explanations = data.explanations as Protocol.Security.SecurityStateExplanation[]; const summary = data.summary as string | null; this._updateSecurityState(securityState, explanations, summary); } _updateVisibleSecurityState(visibleSecurityState: PageVisibleSecurityState): void { this._sidebarMainViewElement.setSecurityState(visibleSecurityState.securityState); this._mainView.updateVisibleSecurityState(visibleSecurityState); } _onVisibleSecurityStateChanged(event: Common.EventTarget.EventTargetEvent): void { const data = event.data as PageVisibleSecurityState; this._updateVisibleSecurityState(data); } selectAndSwitchToMainView(): void { // The sidebar element will trigger displaying the main view. Rather than making a redundant call to display the main view, we rely on this. this._sidebarMainViewElement.select(true); } showOrigin(origin: string): void { const originState = this._origins.get(origin); if (!originState) { return; } if (!originState.originView) { originState.originView = new SecurityOriginView(this, origin, originState); } this._setVisibleView(originState.originView); } wasShown(): void { super.wasShown(); if (!this._visibleView) { this.selectAndSwitchToMainView(); } } focus(): void { this._sidebarTree.focus(); } _setVisibleView(view: UI.Widget.VBox): void { if (this._visibleView === view) { return; } if (this._visibleView) { this._visibleView.detach(); } this._visibleView = view; if (view) { this.splitWidget().setMainWidget(view); } } _onResponseReceived(event: Common.EventTarget.EventTargetEvent): void { const request = event.data.request as SDK.NetworkRequest.NetworkRequest; if (request.resourceType() === Common.ResourceType.resourceTypes.Document) { this._lastResponseReceivedForLoaderId.set(request.loaderId, request); } } _processRequest(request: SDK.NetworkRequest.NetworkRequest): void { const origin = Common.ParsedURL.ParsedURL.extractOrigin(request.url()); if (!origin) { // We don't handle resources like data: URIs. Most of them don't affect the lock icon. return; } let securityState: Protocol.Security.SecurityState.Insecure|Protocol.Security.SecurityState = request.securityState() as Protocol.Security.SecurityState; if (request.mixedContentType === Protocol.Security.MixedContentType.Blockable || request.mixedContentType === Protocol.Security.MixedContentType.OptionallyBlockable) { securityState = Protocol.Security.SecurityState.Insecure; } const originState = this._origins.get(origin); if (originState) { const oldSecurityState = originState.securityState; originState.securityState = this._securityStateMin(oldSecurityState, securityState); if (oldSecurityState !== originState.securityState) { const securityDetails = request.securityDetails() as Protocol.Network.SecurityDetails | null; if (securityDetails) { originState.securityDetails = securityDetails; } this._sidebarTree.updateOrigin(origin, securityState); if (originState.originView) { originState.originView.setSecurityState(securityState); } } } else { // This stores the first security details we see for an origin, but we should // eventually store a (deduplicated) list of all the different security // details we have seen. https://crbug.com/503170 const newOriginState: OriginState = { securityState, securityDetails: request.securityDetails(), loadedFromCache: request.cached(), originView: undefined, }; this._origins.set(origin, newOriginState); this._sidebarTree.addOrigin(origin, securityState); // Don't construct the origin view yet (let it happen lazily). } } _onRequestFinished(event: Common.EventTarget.EventTargetEvent): void { const request = event.data as SDK.NetworkRequest.NetworkRequest; this._updateFilterRequestCounts(request); this._processRequest(request); } _updateFilterRequestCounts(request: SDK.NetworkRequest.NetworkRequest): void { if (request.mixedContentType === Protocol.Security.MixedContentType.None) { return; } let filterKey: string = Network.NetworkLogView.MixedContentFilterValues.All; if (request.wasBlocked()) { filterKey = Network.NetworkLogView.MixedContentFilterValues.Blocked; } else if (request.mixedContentType === Protocol.Security.MixedContentType.Blockable) { filterKey = Network.NetworkLogView.MixedContentFilterValues.BlockOverridden; } else if (request.mixedContentType === Protocol.Security.MixedContentType.OptionallyBlockable) { filterKey = Network.NetworkLogView.MixedContentFilterValues.Displayed; } const currentCount = this._filterRequestCounts.get(filterKey); if (!currentCount) { this._filterRequestCounts.set(filterKey, 1); } else { this._filterRequestCounts.set(filterKey, currentCount + 1); } this._mainView.refreshExplanations(); } filterRequestCount(filterKey: string): number { return this._filterRequestCounts.get(filterKey) || 0; } _securityStateMin(stateA: Protocol.Security.SecurityState, stateB: Protocol.Security.SecurityState): Protocol.Security.SecurityState { return SecurityModel.SecurityStateComparator(stateA, stateB) < 0 ? stateA : stateB; } modelAdded(securityModel: SecurityModel): void { if (this._securityModel) { return; } this._securityModel = securityModel; const resourceTreeModel = securityModel.resourceTreeModel(); const networkManager = securityModel.networkManager(); this._eventListeners = [ securityModel.addEventListener(Events.VisibleSecurityStateChanged, this._onVisibleSecurityStateChanged, this), resourceTreeModel.addEventListener( SDK.ResourceTreeModel.Events.MainFrameNavigated, this._onMainFrameNavigated, this), resourceTreeModel.addEventListener( SDK.ResourceTreeModel.Events.InterstitialShown, this._onInterstitialShown, this), resourceTreeModel.addEventListener( SDK.ResourceTreeModel.Events.InterstitialHidden, this._onInterstitialHidden, this), networkManager.addEventListener(SDK.NetworkManager.Events.ResponseReceived, this._onResponseReceived, this), networkManager.addEventListener(SDK.NetworkManager.Events.RequestFinished, this._onRequestFinished, this), ]; if (resourceTreeModel.isInterstitialShowing()) { this._onInterstitialShown(); } } modelRemoved(securityModel: SecurityModel): void { if (this._securityModel !== securityModel) { return; } this._securityModel = null; Common.EventTarget.EventTarget.removeEventListeners(this._eventListeners); } _onMainFrameNavigated(event: Common.EventTarget.EventTargetEvent): void { const frame = event.data as Protocol.Page.Frame; const request = this._lastResponseReceivedForLoaderId.get(frame.loaderId); this.selectAndSwitchToMainView(); this._sidebarTree.clearOrigins(); this._origins.clear(); this._lastResponseReceivedForLoaderId.clear(); this._filterRequestCounts.clear(); // After clearing the filtered request counts, refresh the // explanations to reflect the new counts. this._mainView.refreshExplanations(); // If we could not find a matching request (as in the case of clicking // through an interstitial, see https://crbug.com/669309), set the origin // based upon the url data from the MainFrameNavigated event itself. const origin = Common.ParsedURL.ParsedURL.extractOrigin(request ? request.url() : frame.url); this._sidebarTree.setMainOrigin(origin); if (request) { this._processRequest(request); } } _onInterstitialShown(): void { // The panel might have been displaying the origin view on the // previously loaded page. When showing an interstitial, switch // back to the Overview view. this.selectAndSwitchToMainView(); this._sidebarTree.toggleOriginsList(true /* hidden */); } _onInterstitialHidden(): void { this._sidebarTree.toggleOriginsList(false /* hidden */); } } export class SecurityPanelSidebarTree extends UI.TreeOutline.TreeOutlineInShadow { _showOriginInPanel: (arg0: Origin) => void; _mainOrigin: string|null; _originGroupTitles: Map<OriginGroup, string>; _originGroups: Map<OriginGroup, UI.TreeOutline.TreeElement>; _elementsByOrigin: Map<string, SecurityPanelSidebarTreeElement>; constructor(mainViewElement: SecurityPanelSidebarTreeElement, showOriginInPanel: (arg0: Origin) => void) { super(); this.registerRequiredCSS('security/sidebar.css', {enableLegacyPatching: true}); this.registerRequiredCSS('security/lockIcon.css', {enableLegacyPatching: true}); this.appendChild(mainViewElement); this._showOriginInPanel = showOriginInPanel; this._mainOrigin = null; this._originGroupTitles = new Map([ [OriginGroup.MainOrigin, i18nString(UIStrings.mainOrigin)], [OriginGroup.NonSecure, i18nString(UIStrings.nonsecureOrigins)], [OriginGroup.Secure, i18nString(UIStrings.secureOrigins)], [OriginGroup.Unknown, i18nString(UIStrings.unknownCanceled)], ]); this._originGroups = new Map(); for (const group of Object.values(OriginGroup)) { const element = this._createOriginGroupElement(this._originGroupTitles.get(group) as string); this._originGroups.set(group, element); this.appendChild(element); } this._clearOriginGroups(); // This message will be removed by clearOrigins() during the first new page load after the panel was opened. const mainViewReloadMessage = new UI.TreeOutline.TreeElement(i18nString(UIStrings.reloadToViewDetails)); mainViewReloadMessage.selectable = false; mainViewReloadMessage.listItemElement.classList.add('security-main-view-reload-message'); const treeElement = this._originGroups.get(OriginGroup.MainOrigin); (treeElement as UI.TreeOutline.TreeElement).appendChild(mainViewReloadMessage); this._elementsByOrigin = new Map(); } _originGroupTitle(originGroup: OriginGroup): string { return /** @type {string} */ this._originGroupTitles.get(originGroup) as string; } _originGroupElement(originGroup: OriginGroup): UI.TreeOutline.TreeElement { return /** @type {!UI.TreeOutline.TreeElement} */ this._originGroups.get(originGroup) as UI.TreeOutline.TreeElement; } _createOriginGroupElement(originGroupTitle: string): UI.TreeOutline.TreeElement { const originGroup = new UI.TreeOutline.TreeElement(originGroupTitle, true); originGroup.selectable = false; originGroup.setCollapsible(false); originGroup.expand(); originGroup.listItemElement.classList.add('security-sidebar-origins'); UI.ARIAUtils.setAccessibleName(originGroup.childrenListElement, originGroupTitle); return originGroup; } toggleOriginsList(hidden: boolean): void { for (const element of this._originGroups.values()) { element.hidden = hidden; } } addOrigin(origin: string, securityState: Protocol.Security.SecurityState): void { const originElement = new SecurityPanelSidebarTreeElement( SecurityPanel.createHighlightedUrl(origin, securityState), this._showOriginInPanel.bind(this, origin), 'security-sidebar-tree-item', 'security-property'); originElement.tooltip = origin; this._elementsByOrigin.set(origin, originElement); this.updateOrigin(origin, securityState); } setMainOrigin(origin: string): void { this._mainOrigin = origin; } updateOrigin(origin: string, securityState: Protocol.Security.SecurityState): void { const originElement = this._elementsByOrigin.get(origin) as SecurityPanelSidebarTreeElement; originElement.setSecurityState(securityState); let newParent: UI.TreeOutline.TreeElement; if (origin === this._mainOrigin) { newParent = this._originGroups.get(OriginGroup.MainOrigin) as UI.TreeOutline.TreeElement; if (securityState === Protocol.Security.SecurityState.Secure) { newParent.title = i18nString(UIStrings.mainOriginSecure); } else { newParent.title = i18nString(UIStrings.mainOriginNonsecure); } UI.ARIAUtils.setAccessibleName(newParent.childrenListElement, newParent.title); } else { switch (securityState) { case Protocol.Security.SecurityState.Secure: newParent = this._originGroupElement(OriginGroup.Secure); break; case Protocol.Security.SecurityState.Unknown: newParent = this._originGroupElement(OriginGroup.Unknown); break; default: newParent = this._originGroupElement(OriginGroup.NonSecure); break; } } const oldParent = originElement.parent; if (oldParent !== newParent) { if (oldParent) { oldParent.removeChild(originElement); if (oldParent.childCount() === 0) { oldParent.hidden = true; } } newParent.appendChild(originElement); newParent.hidden = false; } } _clearOriginGroups(): void { for (const originGroup of this._originGroups.values()) { originGroup.removeChildren(); originGroup.hidden = true; } const mainOrigin = this._originGroupElement(OriginGroup.MainOrigin); mainOrigin.title = this._originGroupTitle(OriginGroup.MainOrigin); mainOrigin.hidden = false; } clearOrigins(): void { this._clearOriginGroups(); this._elementsByOrigin.clear(); } } // TODO(crbug.com/1167717): Make this a const enum again // eslint-disable-next-line rulesdir/const_enum export enum OriginGroup { MainOrigin = 'MainOrigin', NonSecure = 'NonSecure', Secure = 'Secure', Unknown = 'Unknown', } export class SecurityPanelSidebarTreeElement extends UI.TreeOutline.TreeElement { _selectCallback: () => void; _cssPrefix: string; _iconElement: HTMLElement; _securityState: Protocol.Security.SecurityState|null; constructor(textElement: Element, selectCallback: () => void, className: string, cssPrefix: string) { super('', false); this._selectCallback = selectCallback; this._cssPrefix = cssPrefix; this.listItemElement.classList.add(className); this._iconElement = this.listItemElement.createChild('div', 'icon'); this._iconElement.classList.add(this._cssPrefix); this.listItemElement.appendChild(textElement); this._securityState = null; this.setSecurityState(Protocol.Security.SecurityState.Unknown); } setSecurityState(newSecurityState: Protocol.Security.SecurityState): void { if (this._securityState) { this._iconElement.classList.remove(this._cssPrefix + '-' + this._securityState); } this._securityState = newSecurityState; this._iconElement.classList.add(this._cssPrefix + '-' + newSecurityState); } securityState(): Protocol.Security.SecurityState|null { return this._securityState; } onselect(): boolean { this._selectCallback(); return true; } } export class SecurityMainView extends UI.Widget.VBox { _panel: SecurityPanel; _summarySection: HTMLElement; _securityExplanationsMain: HTMLElement; _securityExplanationsExtra: HTMLElement; _lockSpectrum: Map<Protocol.Security.SecurityState, HTMLElement>; _summaryText: HTMLElement; _explanations: (Protocol.Security.SecurityStateExplanation|SecurityStyleExplanation)[]|null; _securityState: Protocol.Security.SecurityState|null; constructor(panel: SecurityPanel) { super(true); this.registerRequiredCSS('security/mainView.css', {enableLegacyPatching: true}); this.registerRequiredCSS('security/lockIcon.css', {enableLegacyPatching: true}); this.setMinimumSize(200, 100); this.contentElement.classList.add('security-main-view'); this._panel = panel; this._summarySection = this.contentElement.createChild('div', 'security-summary'); // Info explanations should appear after all others. this._securityExplanationsMain = this.contentElement.createChild('div', 'security-explanation-list security-explanations-main'); this._securityExplanationsExtra = this.contentElement.createChild('div', 'security-explanation-list security-explanations-extra'); // Fill the security summary section. const summaryDiv = this._summarySection.createChild('div', 'security-summary-section-title'); summaryDiv.textContent = i18nString(UIStrings.securityOverview); UI.ARIAUtils.markAsHeading(summaryDiv, 1); const lockSpectrum = this._summarySection.createChild('div', 'lock-spectrum'); this._lockSpectrum = new Map([ [Protocol.Security.SecurityState.Secure, lockSpectrum.createChild('div', 'lock-icon lock-icon-secure')], [Protocol.Security.SecurityState.Neutral, lockSpectrum.createChild('div', 'lock-icon lock-icon-neutral')], [Protocol.Security.SecurityState.Insecure, lockSpectrum.createChild('div', 'lock-icon lock-icon-insecure')], ]); UI.Tooltip.Tooltip.install( this.getLockSpectrumDiv(Protocol.Security.SecurityState.Secure), i18nString(UIStrings.secure)); UI.Tooltip.Tooltip.install( this.getLockSpectrumDiv(Protocol.Security.SecurityState.Neutral), i18nString(UIStrings.info)); UI.Tooltip.Tooltip.install( this.getLockSpectrumDiv(Protocol.Security.SecurityState.Insecure), i18nString(UIStrings.notSecure)); this._summarySection.createChild('div', 'triangle-pointer-container') .createChild('div', 'triangle-pointer-wrapper') .createChild('div', 'triangle-pointer'); this._summaryText = this._summarySection.createChild('div', 'security-summary-text'); UI.ARIAUtils.markAsHeading(this._summaryText, 2); this._explanations = null; this._securityState = null; } getLockSpectrumDiv(securityState: Protocol.Security.SecurityState): HTMLElement { const element = this._lockSpectrum.get(securityState); if (!element) { throw new Error(`Invalid argument: ${securityState}`); } return element; } _addExplanation(parent: Element, explanation: Protocol.Security.SecurityStateExplanation|SecurityStyleExplanation): Element { const explanationSection = parent.createChild('div', 'security-explanation'); explanationSection.classList.add('security-explanation-' + explanation.securityState); explanationSection.createChild('div', 'security-property') .classList.add('security-property-' + explanation.securityState); const text = explanationSection.createChild('div', 'security-explanation-text'); const explanationHeader = text.createChild('div', 'security-explanation-title'); if (explanation.title) { explanationHeader.createChild('span').textContent = explanation.title + ' - '; explanationHeader.createChild('span', 'security-explanation-title-' + explanation.securityState).textContent = explanation.summary; } else { explanationHeader.textContent = explanation.summary; } text.createChild('div').textContent = explanation.description; if (explanation.certificate.length) { text.appendChild(SecurityPanel.createCertificateViewerButtonForCert( i18nString(UIStrings.viewCertificate), explanation.certificate)); } if (explanation.recommendations && explanation.recommendations.length) { const recommendationList = text.createChild('ul', 'security-explanation-recommendations'); for (const recommendation of explanation.recommendations) { recommendationList.createChild('li').textContent = recommendation; } } return text; } updateSecurityState( newSecurityState: Protocol.Security.SecurityState, explanations: Protocol.Security.SecurityStateExplanation[], summary: string|null): void { // Remove old state. // It's safe to call this even when this._securityState is undefined. this._summarySection.classList.remove('security-summary-' + this._securityState); // Add new state. this._securityState = newSecurityState; this._summarySection.classList.add('security-summary-' + this._securityState); const summaryExplanationStrings = new Map<Protocol.Security.SecurityState, string>([ [Protocol.Security.SecurityState.Unknown, i18nString(UIStrings.theSecurityOfThisPageIsUnknown)], [Protocol.Security.SecurityState.Insecure, i18nString(UIStrings.thisPageIsNotSecure)], [Protocol.Security.SecurityState.Neutral, i18nString(UIStrings.thisPageIsNotSecure)], [Protocol.Security.SecurityState.Secure, i18nString(UIStrings.thisPageIsSecureValidHttps)], [Protocol.Security.SecurityState.InsecureBroken, i18nString(UIStrings.thisPageIsNotSecureBrokenHttps)], ]); // Update the color and title of the triangle icon in the lock spectrum to // match the security state. if (this._securityState === Protocol.Security.SecurityState.Insecure) { this.getLockSpectrumDiv(Protocol.Security.SecurityState.Insecure).classList.add('lock-icon-insecure'); this.getLockSpectrumDiv(Protocol.Security.SecurityState.Insecure).classList.remove('lock-icon-insecure-broken'); UI.Tooltip.Tooltip.install( this.getLockSpectrumDiv(Protocol.Security.SecurityState.Insecure), i18nString(UIStrings.notSecure)); } else if (this._securityState === Protocol.Security.SecurityState.InsecureBroken) { this.getLockSpectrumDiv(Protocol.Security.SecurityState.Insecure).classList.add('lock-icon-insecure-broken'); this.getLockSpectrumDiv(Protocol.Security.SecurityState.Insecure).classList.remove('lock-icon-insecure'); UI.Tooltip.Tooltip.install( this.getLockSpectrumDiv(Protocol.Security.SecurityState.Insecure), i18nString(UIStrings.notSecureBroken)); } // Use override summary if present, otherwise use base explanation this._summaryText.textContent = summary || summaryExplanationStrings.get(this._securityState) || ''; this._explanations = explanations; this.refreshExplanations(); } updateVisibleSecurityState(visibleSecurityState: PageVisibleSecurityState): void { // Remove old state. // It's safe to call this even when this._securityState is undefined. this._summarySection.classList.remove('security-summary-' + this._securityState); // Add new state. this._securityState = visibleSecurityState.securityState; this._summarySection.classList.add('security-summary-' + this._securityState); // Update the color and title of the triangle icon in the lock spectrum to // match the security state. if (this._securityState === Protocol.Security.SecurityState.Insecure) { this.getLockSpectrumDiv(Protocol.Security.SecurityState.Insecure).classList.add('lock-icon-insecure'); this.getLockSpectrumDiv(Protocol.Security.SecurityState.Insecure).classList.remove('lock-icon-insecure-broken'); UI.Tooltip.Tooltip.install( this.getLockSpectrumDiv(Protocol.Security.SecurityState.Insecure), i18nString(UIStrings.notSecure)); } else if (this._securityState === Protocol.Security.SecurityState.InsecureBroken) { this.getLockSpectrumDiv(Protocol.Security.SecurityState.Insecure).classList.add('lock-icon-insecure-broken'); this.getLockSpectrumDiv(Protocol.Security.SecurityState.Insecure).classList.remove('lock-icon-insecure'); UI.Tooltip.Tooltip.install( this.getLockSpectrumDiv(Protocol.Security.SecurityState.Insecure), i18nString(UIStrings.notSecureBroken)); } const {summary, explanations} = this._getSecuritySummaryAndExplanations(visibleSecurityState); // Use override summary if present, otherwise use base explanation this._summaryText.textContent = summary || SummaryMessages[this._securityState]; this._explanations = this._orderExplanations(explanations); this.refreshExplanations(); } _getSecuritySummaryAndExplanations(visibleSecurityState: PageVisibleSecurityState): {summary: (string|undefined), explanations: Array<SecurityStyleExplanation>} { const {securityState, securityStateIssueIds} = visibleSecurityState; let summary; const explanations: SecurityStyleExplanation[] = []; summary = this._explainSafetyTipSecurity(visibleSecurityState, summary, explanations); if (securityStateIssueIds.includes('malicious-content')) { summary = i18nString(UIStrings.thisPageIsDangerousFlaggedBy); // Always insert SafeBrowsing explanation at the front. explanations.unshift(new SecurityStyleExplanation( Protocol.Security.SecurityState.Insecure, undefined, i18nString(UIStrings.flaggedByGoogleSafeBrowsing), i18nString(UIStrings.toCheckThisPagesStatusVisit))); } else if ( securityStateIssueIds.includes('is-error-page') && (visibleSecurityState.certificateSecurityState === null || visibleSecurityState.certificateSecurityState.certificateNetworkError === null)) { summary = i18nString(UIStrings.thisIsAnErrorPage); // In the case of a non cert error page, we usually don't have a // certificate, connection, or content that needs to be explained, e.g. in // the case of a net error, so we can early return. return {summary, explanations}; } else if ( securityState === Protocol.Security.SecurityState.InsecureBroken && securityStateIssueIds.includes('scheme-is-not-cryptographic')) { summary = summary || i18nString(UIStrings.thisPageIsInsecureUnencrypted); if (securityStateIssueIds.includes('insecure-input-events')) { explanations.push(new SecurityStyleExplanation( Protocol.Security.SecurityState.Insecure, undefined, i18nString(UIStrings.formFieldEditedOnANonsecurePage), i18nString(UIStrings.dataWasEnteredInAFieldOnA))); } } if (securityStateIssueIds.includes('scheme-is-not-cryptographic')) { if (securityState === Protocol.Security.SecurityState.Neutral && !securityStateIssueIds.includes('insecure-origin')) { summary = i18nString(UIStrings.thisPageHasANonhttpsSecureOrigin); } return {summary, explanations}; } this._explainCertificateSecurity(visibleSecurityState, explanations); this._explainConnectionSecurity(visibleSecurityState, explanations); this._explainContentSecurity(visibleSecurityState, explanations); return {summary, explanations}; } _explainSafetyTipSecurity( visibleSecurityState: PageVisibleSecurityState, summary: string|undefined, explanations: SecurityStyleExplanation[]): string|undefined { const {securityStateIssueIds, safetyTipInfo} = visibleSecurityState; const currentExplanations = []; if (securityStateIssueIds.includes('bad_reputation')) { const formatedDescription = `${i18nString(UIStrings.chromeHasDeterminedThatThisSiteS)}\n\n${ i18nString(UIStrings.ifYouBelieveThisIsShownIn)}`; currentExplanations.push({ summary: i18nString(UIStrings.thisPageIsSuspicious), description: formatedDescription, }); } else if (securityStateIssueIds.includes('lookalike') && safetyTipInfo && safetyTipInfo.safeUrl) { const hostname = new URL(safetyTipInfo.safeUrl).hostname; const hostnamePlaceholder = {PH1: hostname}; const formatedDescriptionSafety = `${i18nString(UIStrings.thisSitesHostnameLooksSimilarToP, hostnamePlaceholder)}\n\n${ i18nString(UIStrings.ifYouBelieveThisIsShownInErrorSafety)}`; currentExplanations.push( {summary: i18nString(UIStrings.possibleSpoofingUrl), description: formatedDescriptionSafety}); } if (currentExplanations.length > 0) { // To avoid overwriting SafeBrowsing's title, set