UNPKG

@quick-game/cli

Version:

Command line interface for rapid qg development

1,059 lines 77 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. import * as Common from '../../core/common/common.js'; import * as Host from '../../core/host/host.js'; import * as i18n from '../../core/i18n/i18n.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as NetworkForward from '../../panels/network/forward/forward.js'; import * as UI from '../../ui/legacy/legacy.js'; import lockIconStyles from './lockIcon.css.js'; import mainViewStyles from './mainView.css.js'; import originViewStyles from './originView.css.js'; import sidebarStyles from './sidebar.css.js'; import { Events, SecurityModel, SecurityStyleExplanation, SummaryMessages, } from './SecurityModel.js'; 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 */ 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 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://g.co/chrome/lookalike-warnings.', /** *@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://g.co/chrome/lookalike-warnings.', /** *@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 Link text in the Security Panel. Clicking the link navigates the user to the * Network panel. Requests refers to network requests. Each request is a piece of data transmitted * from the current user's browser to a remote server. */ viewDRequestsInNetworkPanel: '{n, plural, =1 {View # request in Network Panel} other {View # 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 the Security panel that refers to how the TLS handshake *established encryption keys. */ keyExchange: 'Key exchange', /** *@description Text in Security Panel that refers to how the TLS handshake *encrypted data. */ cipher: 'Cipher', /** *@description Text in Security Panel that refers to the signature algorithm *used by the server for authenticate in the TLS handshake. */ serverSignature: 'Server signature', /** *@description Text in Security Panel that refers to whether the ClientHello *message in the TLS handshake was encrypted. */ encryptedClientHello: 'Encrypted ClientHello', /** *@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 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)', /** *@description Shown when a field refers to an option that is unknown to the frontend. */ unknownField: 'unknown', /** *@description Shown when a field refers to a TLS feature which was enabled. */ enabled: 'enabled', }; const str_ = i18n.i18n.registerUIStrings('panels/security/SecurityPanel.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); let securityPanelInstance; // See https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-signaturescheme // This contains signature schemes supported by Chrome. const SignatureSchemeStrings = new Map([ // The full name for these schemes is RSASSA-PKCS1-v1_5, sometimes // "PKCS#1 v1.5", but those are very long, so let "RSA" vs "RSA-PSS" // disambiguate. [0x0201, 'RSA with SHA-1'], [0x0401, 'RSA with SHA-256'], [0x0501, 'RSA with SHA-384'], [0x0601, 'RSA with SHA-512'], // We omit the curve from these names because in TLS 1.2 these code points // were not specific to a curve. Saying "P-256" for a server that used a P-384 // key with SHA-256 in TLS 1.2 would be confusing. [0x0403, 'ECDSA with SHA-256'], [0x0503, 'ECDSA with SHA-384'], [0x0804, 'RSA-PSS with SHA-256'], [0x0805, 'RSA-PSS with SHA-384'], [0x0806, 'RSA-PSS with SHA-512'], ]); export class SecurityPanel extends UI.Panel.PanelWithSidebar { mainView; sidebarMainViewElement; sidebarTree; lastResponseReceivedForLoaderId; origins; filterRequestCounts; visibleView; eventListeners; securityModel; 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(); this.visibleView = null; this.eventListeners = []; this.securityModel = null; SDK.TargetManager.TargetManager.instance().observeModels(SecurityModel, this, { scoped: true }); SDK.TargetManager.TargetManager.instance().addModelListener(SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.PrimaryPageChanged, this.onPrimaryPageChanged, this); } static instance(opts = { forceNew: null }) { const { forceNew } = opts; if (!securityPanelInstance || forceNew) { securityPanelInstance = new SecurityPanel(); } return securityPanelInstance; } static createCertificateViewerButtonForOrigin(text, origin) { const certificateButton = UI.UIUtils.createTextButton(text, async (e) => { 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, names) { 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, securityState) { 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; } updateVisibleSecurityState(visibleSecurityState) { this.sidebarMainViewElement.setSecurityState(visibleSecurityState.securityState); this.mainView.updateVisibleSecurityState(visibleSecurityState); } onVisibleSecurityStateChanged({ data }) { this.updateVisibleSecurityState(data); } selectAndSwitchToMainView() { // 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) { const originState = this.origins.get(origin); if (!originState) { return; } if (!originState.originView) { originState.originView = new SecurityOriginView(this, origin, originState); } this.setVisibleView(originState.originView); } wasShown() { super.wasShown(); if (!this.visibleView) { this.selectAndSwitchToMainView(); } } focus() { this.sidebarTree.focus(); } setVisibleView(view) { if (this.visibleView === view) { return; } if (this.visibleView) { this.visibleView.detach(); } this.visibleView = view; if (view) { this.splitWidget().setMainWidget(view); } } onResponseReceived(event) { const request = event.data.request; if (request.resourceType() === Common.ResourceType.resourceTypes.Document && request.loaderId) { this.lastResponseReceivedForLoaderId.set(request.loaderId, request); } } processRequest(request) { 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 = request.securityState(); if (request.mixedContentType === "blockable" /* Protocol.Security.MixedContentType.Blockable */ || request.mixedContentType === "optionally-blockable" /* Protocol.Security.MixedContentType.OptionallyBlockable */) { securityState = "insecure" /* 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(); 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 = { 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) { const request = event.data; this.updateFilterRequestCounts(request); this.processRequest(request); } updateFilterRequestCounts(request) { if (request.mixedContentType === "none" /* Protocol.Security.MixedContentType.None */) { return; } let filterKey = NetworkForward.UIFilter.MixedContentFilterValues.All; if (request.wasBlocked()) { filterKey = NetworkForward.UIFilter.MixedContentFilterValues.Blocked; } else if (request.mixedContentType === "blockable" /* Protocol.Security.MixedContentType.Blockable */) { filterKey = NetworkForward.UIFilter.MixedContentFilterValues.BlockOverridden; } else if (request.mixedContentType === "optionally-blockable" /* Protocol.Security.MixedContentType.OptionallyBlockable */) { filterKey = NetworkForward.UIFilter.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) { return this.filterRequestCounts.get(filterKey) || 0; } securityStateMin(stateA, stateB) { return SecurityModel.SecurityStateComparator(stateA, stateB) < 0 ? stateA : stateB; } modelAdded(securityModel) { if (securityModel.target() !== securityModel.target().outermostTarget()) { return; } this.securityModel = securityModel; const resourceTreeModel = securityModel.resourceTreeModel(); const networkManager = securityModel.networkManager(); if (this.eventListeners.length) { Common.EventTarget.removeEventListeners(this.eventListeners); } this.eventListeners = [ securityModel.addEventListener(Events.VisibleSecurityStateChanged, this.onVisibleSecurityStateChanged, 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) { if (this.securityModel !== securityModel) { return; } this.securityModel = null; Common.EventTarget.removeEventListeners(this.eventListeners); } onPrimaryPageChanged(event) { const { frame } = event.data; 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 PrimaryPageChanged event itself. const origin = Common.ParsedURL.ParsedURL.extractOrigin(request ? request.url() : frame.url); this.sidebarTree.setMainOrigin(origin); if (request) { this.processRequest(request); } } onInterstitialShown() { // 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() { this.sidebarTree.toggleOriginsList(false /* hidden */); } } export class SecurityPanelSidebarTree extends UI.TreeOutline.TreeOutlineInShadow { showOriginInPanel; mainOrigin; originGroupTitles; originGroups; elementsByOrigin; mainViewReloadMessage; constructor(mainViewElement, showOriginInPanel) { super(); this.appendChild(mainViewElement); this.registerCSSFiles([lockIconStyles, sidebarStyles]); 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)); this.originGroups.set(group, element); this.appendChild(element); } this.mainViewReloadMessage = new UI.TreeOutline.TreeElement(i18nString(UIStrings.reloadToViewDetails)); this.mainViewReloadMessage.selectable = false; this.mainViewReloadMessage.listItemElement.classList.add('security-main-view-reload-message'); const treeElement = this.originGroups.get(OriginGroup.MainOrigin); treeElement.appendChild(this.mainViewReloadMessage); this.clearOriginGroups(); this.elementsByOrigin = new Map(); } originGroupTitle(originGroup) { return this.originGroupTitles.get(originGroup); } originGroupElement(originGroup) { return this.originGroups.get(originGroup); } createOriginGroupElement(originGroupTitle) { 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.setLabel(originGroup.childrenListElement, originGroupTitle); return originGroup; } toggleOriginsList(hidden) { for (const element of this.originGroups.values()) { element.hidden = hidden; } } addOrigin(origin, securityState) { this.mainViewReloadMessage.hidden = true; 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) { this.mainOrigin = origin; } updateOrigin(origin, securityState) { const originElement = this.elementsByOrigin.get(origin); originElement.setSecurityState(securityState); let newParent; if (origin === this.mainOrigin) { newParent = this.originGroups.get(OriginGroup.MainOrigin); if (securityState === "secure" /* Protocol.Security.SecurityState.Secure */) { newParent.title = i18nString(UIStrings.mainOriginSecure); } else { newParent.title = i18nString(UIStrings.mainOriginNonsecure); } UI.ARIAUtils.setLabel(newParent.childrenListElement, newParent.title); } else { switch (securityState) { case "secure" /* Protocol.Security.SecurityState.Secure */: newParent = this.originGroupElement(OriginGroup.Secure); break; case "unknown" /* 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() { for (const [originGroup, originGroupElement] of this.originGroups) { if (originGroup === OriginGroup.MainOrigin) { for (let i = originGroupElement.childCount() - 1; i > 0; i--) { originGroupElement.removeChildAtIndex(i); } originGroupElement.title = this.originGroupTitle(OriginGroup.MainOrigin); originGroupElement.hidden = false; this.mainViewReloadMessage.hidden = false; } else { originGroupElement.removeChildren(); originGroupElement.hidden = true; } } } clearOrigins() { this.clearOriginGroups(); this.elementsByOrigin.clear(); } wasShown() { } } // TODO(crbug.com/1167717): Make this a const enum again // eslint-disable-next-line rulesdir/const_enum export var OriginGroup; (function (OriginGroup) { OriginGroup["MainOrigin"] = "MainOrigin"; OriginGroup["NonSecure"] = "NonSecure"; OriginGroup["Secure"] = "Secure"; OriginGroup["Unknown"] = "Unknown"; })(OriginGroup || (OriginGroup = {})); export class SecurityPanelSidebarTreeElement extends UI.TreeOutline.TreeElement { selectCallback; cssPrefix; iconElement; securityStateInternal; constructor(textElement, selectCallback, className, cssPrefix) { 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.securityStateInternal = null; this.setSecurityState("unknown" /* Protocol.Security.SecurityState.Unknown */); } setSecurityState(newSecurityState) { if (this.securityStateInternal) { this.iconElement.classList.remove(this.cssPrefix + '-' + this.securityStateInternal); } this.securityStateInternal = newSecurityState; this.iconElement.classList.add(this.cssPrefix + '-' + newSecurityState); } securityState() { return this.securityStateInternal; } onselect() { this.selectCallback(); return true; } } export class SecurityMainView extends UI.Widget.VBox { panel; summarySection; securityExplanationsMain; securityExplanationsExtra; lockSpectrum; summaryText; explanations; securityState; constructor(panel) { super(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([ ["secure" /* Protocol.Security.SecurityState.Secure */, lockSpectrum.createChild('div', 'lock-icon lock-icon-secure')], ["neutral" /* Protocol.Security.SecurityState.Neutral */, lockSpectrum.createChild('div', 'lock-icon lock-icon-neutral')], ["insecure" /* Protocol.Security.SecurityState.Insecure */, lockSpectrum.createChild('div', 'lock-icon lock-icon-insecure')], ]); UI.Tooltip.Tooltip.install(this.getLockSpectrumDiv("secure" /* Protocol.Security.SecurityState.Secure */), i18nString(UIStrings.secure)); UI.Tooltip.Tooltip.install(this.getLockSpectrumDiv("neutral" /* Protocol.Security.SecurityState.Neutral */), i18nString(UIStrings.info)); UI.Tooltip.Tooltip.install(this.getLockSpectrumDiv("insecure" /* 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) { const element = this.lockSpectrum.get(securityState); if (!element) { throw new Error(`Invalid argument: ${securityState}`); } return element; } addExplanation(parent, explanation) { 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; } updateVisibleSecurityState(visibleSecurityState) { // 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 === "insecure" /* Protocol.Security.SecurityState.Insecure */) { this.getLockSpectrumDiv("insecure" /* Protocol.Security.SecurityState.Insecure */).classList.add('lock-icon-insecure'); this.getLockSpectrumDiv("insecure" /* Protocol.Security.SecurityState.Insecure */).classList.remove('lock-icon-insecure-broken'); UI.Tooltip.Tooltip.install(this.getLockSpectrumDiv("insecure" /* Protocol.Security.SecurityState.Insecure */), i18nString(UIStrings.notSecure)); } else if (this.securityState === "insecure-broken" /* Protocol.Security.SecurityState.InsecureBroken */) { this.getLockSpectrumDiv("insecure" /* Protocol.Security.SecurityState.Insecure */).classList.add('lock-icon-insecure-broken'); this.getLockSpectrumDiv("insecure" /* Protocol.Security.SecurityState.Insecure */).classList.remove('lock-icon-insecure'); UI.Tooltip.Tooltip.install(this.getLockSpectrumDiv("insecure" /* 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) { const { securityState, securityStateIssueIds } = visibleSecurityState; let summary; const explanations = []; 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("insecure" /* 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 === "insecure-broken" /* Protocol.Security.SecurityState.InsecureBroken */ && securityStateIssueIds.includes('scheme-is-not-cryptographic')) { summary = summary || i18nString(UIStrings.thisPageIsInsecureUnencrypted); } if (securityStateIssueIds.includes('scheme-is-not-cryptographic')) { if (securityState === "neutral" /* 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, summary, explanations) { 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 the main summary only if // it's empty. The title set here can be overridden by later checks (e.g. // bad HTTP). summary = summary || i18nString(UIStrings.thisPageIsSuspiciousFlaggedBy); explanations.push(new SecurityStyleExplanation("insecure" /* Protocol.Security.SecurityState.Insecure */, undefined, currentExplanations[0].summary, currentExplanations[0].description)); } return summary; } explainCertificateSecurity(visibleSecurityState, explanations) { const { certificateSecurityState, securityStateIssueIds } = visibleSecurityState; const title = i18nString(UIStrings.certificate); if (certificateSecurityState && certificateSecurityState.certificateHasSha1Signature) { const explanationSummary = i18nString(UIStrings.insecureSha); const description = i18nString(UIStrings.theCertificateChainForThisSite); if (certificateSecurityState.certificateHasWeakSignature) { explanations.push(new SecurityStyleExplanation("insecure" /* Protocol.Security.SecurityState.Insecure */, title, explanationSummary, description, certificateSecurityState.certificate, "none" /* Protocol.Security.MixedContentType.None */)); } else { explanations.push(new SecurityStyleExplanation("neutral" /* Protocol.Security.SecurityState.Neutral */, title, explanationSummary, description, certificateSecurityState.certificate, "none" /* Protocol.Security.MixedContentType.None */)); } } if (certificateSecurityState && securityStateIssueIds.includes('cert-missing-subject-alt-name')) { explanations.push(new SecurityStyleExplanation("insecure" /* Protocol.Security.SecurityState.Insecure */, title, i18nString(UIStrings.subjectAlternativeNameMissing), i18nString(UIStrings.theCertificateForThisSiteDoesNot), certificateSecurityState.certificate, "none" /* Protocol.Security.MixedContentType.None */)); } if (certificateSecurityState && certificateSecurityState.certificateNetworkError !== null) { expl