diff --git a/browser/base/content/tabbrowser.js b/browser/base/content/tabbrowser.js index fc0edd67e572..132fe683f234 100644 --- a/browser/base/content/tabbrowser.js +++ b/browser/base/content/tabbrowser.js @@ -778,7 +778,7 @@ window._gBrowser = { try { // Use the passed in resolvedURI if we have one - const resolvedURI = aResolvedURI || Services.io.newChannelFromURI2( + const resolvedURI = aResolvedURI || Services.io.newChannelFromURI( aURI, null, // loadingNode Services.scriptSecurityManager.getSystemPrincipal(), // loadingPrincipal diff --git a/browser/components/sessionstore/SessionStore.jsm b/browser/components/sessionstore/SessionStore.jsm index 68705dfc962c..576c7e8d5465 100644 --- a/browser/components/sessionstore/SessionStore.jsm +++ b/browser/components/sessionstore/SessionStore.jsm @@ -1591,7 +1591,7 @@ var SessionStoreInternal = { onQuitApplicationGranted: function ssi_onQuitApplicationGranted(syncShutdown = false) { // Collect an initial snapshot of window data before we do the flush. let index = 0; - for (let window of this._browserWindows) { + for (let window of this._orderedBrowserWindows) { this._collectWindowData(window); this._windows[window.__SSi].zIndex = ++index; } @@ -3416,7 +3416,7 @@ var SessionStoreInternal = { if (RunState.isRunning) { // update the data for all windows with activities since the last save operation. let index = 0; - for (let window of this._browserWindows) { + for (let window of this._orderedBrowserWindows) { if (!this._isWindowLoaded(window)) // window data is still in _statesToRestore continue; if (aUpdateAll || DirtyWindows.has(window) || window == activeWindow) { @@ -4527,8 +4527,10 @@ var SessionStoreInternal = { }, /** - * Iterator that yields all currently opened browser windows, in order. + * Iterator that yields all currently opened browser windows. * (Might miss the most recent one.) + * This list is in focus order, but may include minimized windows + * before non-minimized windows. */ _browserWindows: { * [Symbol.iterator]() { @@ -4539,6 +4541,30 @@ var SessionStoreInternal = { }, }, + /** + * Iterator that yields all currently opened browser windows, + * with minimized windows last. + * (Might miss the most recent window.) + */ + _orderedBrowserWindows: { + * [Symbol.iterator]() { + let windows = BrowserWindowTracker.orderedWindows; + windows.sort((a, b) => { + if (a.windowState == a.STATE_MINIMIZED && b.windowState != b.STATE_MINIMIZED) { + return 1; + } + if (a.windowState != a.STATE_MINIMIZED && b.windowState == b.STATE_MINIMIZED) { + return -1; + } + return 0; + }); + for (let window of windows) { + if (window.__SSi && !window.closed) + yield window; + } + }, + }, + /** * Returns most recent window * @returns Window reference diff --git a/browser/modules/BrowserWindowTracker.jsm b/browser/modules/BrowserWindowTracker.jsm index 1b7b407dcf59..03ee9d11c2aa 100644 --- a/browser/modules/BrowserWindowTracker.jsm +++ b/browser/modules/BrowserWindowTracker.jsm @@ -19,7 +19,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { // Constants const TAB_EVENTS = ["TabBrowserInserted", "TabSelect"]; -const WINDOW_EVENTS = ["activate", "sizemodechange", "unload"]; +const WINDOW_EVENTS = ["activate", "unload"]; const DEBUG = false; // Variables @@ -64,9 +64,6 @@ function _handleEvent(event) { case "activate": WindowHelper.onActivate(event.target); break; - case "sizemodechange": - WindowHelper.onSizemodeChange(event.target); - break; case "unload": WindowHelper.removeWindow(event.currentTarget); break; @@ -144,14 +141,6 @@ var WindowHelper = { _updateCurrentContentOuterWindowID(window.gBrowser.selectedBrowser); }, - - onSizemodeChange(window) { - if (window.windowState == window.STATE_MINIMIZED) { - // Make sure to have the minimized window behind unminimized windows. - _untrackWindowOrder(window); - _trackWindowOrder(window); - } - }, }; this.BrowserWindowTracker = { diff --git a/browser/modules/FaviconLoader.jsm b/browser/modules/FaviconLoader.jsm index 920b0ef236f5..de93250ba658 100644 --- a/browser/modules/FaviconLoader.jsm +++ b/browser/modules/FaviconLoader.jsm @@ -90,7 +90,7 @@ class FaviconLoad { constructor(iconInfo) { this.icon = iconInfo; - this.channel = Services.io.newChannelFromURI2( + this.channel = Services.io.newChannelFromURI( iconInfo.iconUri, iconInfo.node, iconInfo.node.nodePrincipal, diff --git a/browser/modules/test/browser/browser_BrowserWindowTracker.js b/browser/modules/test/browser/browser_BrowserWindowTracker.js index b351e49a290e..d02a9a6b60b2 100644 --- a/browser/modules/test/browser/browser_BrowserWindowTracker.js +++ b/browser/modules/test/browser/browser_BrowserWindowTracker.js @@ -110,16 +110,5 @@ add_task(async function test_orderedWindows() { let expected = [1, 6, 4, 9, 8, 7, 5, 3, 2, 0]; Assert.deepEqual(expected, ordered2.map(w => windows.indexOf(w)), "After shuffle of focused windows, the order should've changed."); - - // Minimizing a window should put it at the end of the ordered list of windows. - let promise = BrowserTestUtils.waitForEvent(windows[9], "sizemodechange"); - windows[9].minimize(); - await promise; - - let ordered3 = BrowserWindowTracker.orderedWindows.filter(w => w != TEST_WINDOW); - // Test the end of the array of window indices, because Windows Debug builds - // mysteriously swap the order of the first two windows. - Assert.deepEqual([8, 7, 5, 3, 2, 0, 9], ordered3.map(w => windows.indexOf(w)).slice(3), - "When a window is minimized, the order should've changed."); }); }); diff --git a/devtools/client/inspector/inspector.js b/devtools/client/inspector/inspector.js index 78991af95999..9e04c302fc3a 100644 --- a/devtools/client/inspector/inspector.js +++ b/devtools/client/inspector/inspector.js @@ -28,12 +28,7 @@ loader.lazyRequireGetter(this, "InspectorSearch", "devtools/client/inspector/ins loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/inspector/toolsidebar", true); loader.lazyRequireGetter(this, "MarkupView", "devtools/client/inspector/markup/markup"); loader.lazyRequireGetter(this, "HighlightersOverlay", "devtools/client/inspector/shared/highlighters-overlay"); -loader.lazyRequireGetter(this, "nodeConstants", "devtools/shared/dom-node-constants"); -loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu"); -loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item"); loader.lazyRequireGetter(this, "ExtensionSidebar", "devtools/client/inspector/extensions/extension-sidebar"); -loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard"); -loader.lazyRequireGetter(this, "openContentLink", "devtools/client/shared/link", true); loader.lazyRequireGetter(this, "saveScreenshot", "devtools/shared/screenshot/save"); loader.lazyImporter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm"); @@ -41,9 +36,6 @@ loader.lazyImporter(this, "DeferredTask", "resource://gre/modules/DeferredTask.j const {LocalizationHelper, localizeMarkup} = require("devtools/shared/l10n"); const INSPECTOR_L10N = new LocalizationHelper("devtools/client/locales/inspector.properties"); -loader.lazyGetter(this, "TOOLBOX_L10N", function() { - return new LocalizationHelper("devtools/client/locales/toolbox.properties"); -}); // Sidebar dimensions const INITIAL_SIDEBAR_SIZE = 350; @@ -125,12 +117,9 @@ function Inspector(toolbox) { // telemetry counts in the Grid Inspector are not double counted on reload. this.previousURL = this.target.url; - this.nodeMenuTriggerInfo = null; - this._clearSearchResultsLabel = this._clearSearchResultsLabel.bind(this); this._handleRejectionIfNotDestroyed = this._handleRejectionIfNotDestroyed.bind(this); this._onBeforeNavigate = this._onBeforeNavigate.bind(this); - this._onContextMenu = this._onContextMenu.bind(this); this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this); this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this); @@ -1402,8 +1391,6 @@ Inspector.prototype = { if (this._markupFrame) { this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true); - this._markupFrame.contentWindow.removeEventListener("contextmenu", - this._onContextMenu); } if (this._search) { @@ -1449,449 +1436,6 @@ Inspector.prototype = { return this._panelDestroyer; }, - /** - * Returns the clipboard content if it is appropriate for pasting - * into the current node's outer HTML, otherwise returns null. - */ - _getClipboardContentForPaste: function() { - const content = clipboardHelper.getText(); - if (content && content.trim().length > 0) { - return content; - } - return null; - }, - - _onContextMenu: function(e) { - if (!(e.originalTarget instanceof Element) || - e.originalTarget.closest("input[type=text]") || - e.originalTarget.closest("input:not([type])") || - e.originalTarget.closest("textarea")) { - return; - } - - e.stopPropagation(); - e.preventDefault(); - - this._openMenu({ - screenX: e.screenX, - screenY: e.screenY, - target: e.target, - }); - }, - - _openMenu: function({ target, screenX = 0, screenY = 0 } = { }) { - if (this.selection.isSlotted()) { - // Slotted elements should not show any context menu. - return null; - } - - const markupContainer = this.markup.getContainer(this.selection.nodeFront); - - this.contextMenuTarget = target; - this.nodeMenuTriggerInfo = markupContainer && - markupContainer.editor.getInfoAtNode(target); - - const isSelectionElement = this.selection.isElementNode() && - !this.selection.isPseudoElementNode(); - const isEditableElement = isSelectionElement && - !this.selection.isAnonymousNode(); - const isDuplicatableElement = isSelectionElement && - !this.selection.isAnonymousNode() && - !this.selection.isRoot(); - const isScreenshotable = isSelectionElement && - this.selection.nodeFront.isTreeDisplayed; - - const menu = new Menu(); - menu.append(new MenuItem({ - id: "node-menu-edithtml", - label: INSPECTOR_L10N.getStr("inspectorHTMLEdit.label"), - accesskey: INSPECTOR_L10N.getStr("inspectorHTMLEdit.accesskey"), - disabled: !isEditableElement, - click: () => this.editHTML(), - })); - menu.append(new MenuItem({ - id: "node-menu-add", - label: INSPECTOR_L10N.getStr("inspectorAddNode.label"), - accesskey: INSPECTOR_L10N.getStr("inspectorAddNode.accesskey"), - disabled: !this.canAddHTMLChild(), - click: () => this.addNode(), - })); - menu.append(new MenuItem({ - id: "node-menu-duplicatenode", - label: INSPECTOR_L10N.getStr("inspectorDuplicateNode.label"), - disabled: !isDuplicatableElement, - click: () => this.duplicateNode(), - })); - menu.append(new MenuItem({ - id: "node-menu-delete", - label: INSPECTOR_L10N.getStr("inspectorHTMLDelete.label"), - accesskey: INSPECTOR_L10N.getStr("inspectorHTMLDelete.accesskey"), - disabled: !this.isDeletable(this.selection.nodeFront), - click: () => this.deleteNode(), - })); - - menu.append(new MenuItem({ - label: INSPECTOR_L10N.getStr("inspectorAttributesSubmenu.label"), - accesskey: - INSPECTOR_L10N.getStr("inspectorAttributesSubmenu.accesskey"), - submenu: this._getAttributesSubmenu(isEditableElement), - })); - - menu.append(new MenuItem({ - type: "separator", - })); - - // Set the pseudo classes - for (const name of ["hover", "active", "focus", "focus-within"]) { - const menuitem = new MenuItem({ - id: "node-menu-pseudo-" + name, - label: name, - type: "checkbox", - click: this.togglePseudoClass.bind(this, ":" + name), - }); - - if (isSelectionElement) { - const checked = this.selection.nodeFront.hasPseudoClassLock(":" + name); - menuitem.checked = checked; - } else { - menuitem.disabled = true; - } - - menu.append(menuitem); - } - - menu.append(new MenuItem({ - type: "separator", - })); - - menu.append(new MenuItem({ - label: INSPECTOR_L10N.getStr("inspectorCopyHTMLSubmenu.label"), - submenu: this._getCopySubmenu(markupContainer, isSelectionElement), - })); - - menu.append(new MenuItem({ - label: INSPECTOR_L10N.getStr("inspectorPasteHTMLSubmenu.label"), - submenu: this._getPasteSubmenu(isEditableElement), - })); - - menu.append(new MenuItem({ - type: "separator", - })); - - const isNodeWithChildren = this.selection.isNode() && - markupContainer.hasChildren; - menu.append(new MenuItem({ - id: "node-menu-expand", - label: INSPECTOR_L10N.getStr("inspectorExpandNode.label"), - disabled: !isNodeWithChildren, - click: () => this.expandNode(), - })); - menu.append(new MenuItem({ - id: "node-menu-collapse", - label: INSPECTOR_L10N.getStr("inspectorCollapseAll.label"), - disabled: !isNodeWithChildren || !markupContainer.expanded, - click: () => this.collapseAll(), - })); - - menu.append(new MenuItem({ - type: "separator", - })); - - menu.append(new MenuItem({ - id: "node-menu-scrollnodeintoview", - label: INSPECTOR_L10N.getStr("inspectorScrollNodeIntoView.label"), - accesskey: - INSPECTOR_L10N.getStr("inspectorScrollNodeIntoView.accesskey"), - disabled: !isSelectionElement, - click: () => this.scrollNodeIntoView(), - })); - menu.append(new MenuItem({ - id: "node-menu-screenshotnode", - label: INSPECTOR_L10N.getStr("inspectorScreenshotNode.label"), - disabled: !isScreenshotable, - click: () => this.screenshotNode().catch(console.error), - })); - menu.append(new MenuItem({ - id: "node-menu-useinconsole", - label: INSPECTOR_L10N.getStr("inspectorUseInConsole.label"), - click: () => this.useInConsole(), - })); - menu.append(new MenuItem({ - id: "node-menu-showdomproperties", - label: INSPECTOR_L10N.getStr("inspectorShowDOMProperties.label"), - click: () => this.showDOMProperties(), - })); - - if (this.selection.nodeFront.customElementLocation) { - menu.append(new MenuItem({ - type: "separator", - })); - - menu.append(new MenuItem({ - id: "node-menu-jumptodefinition", - label: INSPECTOR_L10N.getStr("inspectorCustomElementDefinition.label"), - click: () => this.jumpToCustomElementDefinition(), - })); - } - - this.buildA11YMenuItem(menu); - - const nodeLinkMenuItems = this._getNodeLinkMenuItems(); - if (nodeLinkMenuItems.filter(item => item.visible).length > 0) { - menu.append(new MenuItem({ - id: "node-menu-link-separator", - type: "separator", - })); - } - - for (const menuitem of nodeLinkMenuItems) { - menu.append(menuitem); - } - - menu.popup(screenX, screenY, this._toolbox); - return menu; - }, - - buildA11YMenuItem: function(menu) { - if (!(this.selection.isElementNode() || this.selection.isTextNode()) || - !Services.prefs.getBoolPref("devtools.accessibility.enabled")) { - return; - } - - const showA11YPropsItem = new MenuItem({ - id: "node-menu-showaccessibilityproperties", - label: INSPECTOR_L10N.getStr("inspectorShowAccessibilityProperties.label"), - click: () => this.showAccessibilityProperties(), - disabled: true, - }); - // Only attempt to determine if a11y props menu item needs to be enabled if - // AccessibilityFront is enabled. - if (this.accessibilityFront.enabled) { - this._updateA11YMenuItem(showA11YPropsItem); - } - - menu.append(showA11YPropsItem); - }, - - _updateA11YMenuItem: async function(menuItem) { - const hasMethod = await this.target.actorHasMethod("domwalker", - "hasAccessibilityProperties"); - if (!hasMethod) { - return; - } - - const hasA11YProps = await this.walker.hasAccessibilityProperties( - this.selection.nodeFront); - if (hasA11YProps) { - this._toolbox.doc.getElementById(menuItem.id).disabled = menuItem.disabled = false; - } - - this.emit("node-menu-updated"); - }, - - _getCopySubmenu: function(markupContainer, isSelectionElement) { - const copySubmenu = new Menu(); - copySubmenu.append(new MenuItem({ - id: "node-menu-copyinner", - label: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.label"), - accesskey: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.accesskey"), - disabled: !isSelectionElement, - click: () => this.copyInnerHTML(), - })); - copySubmenu.append(new MenuItem({ - id: "node-menu-copyouter", - label: INSPECTOR_L10N.getStr("inspectorCopyOuterHTML.label"), - accesskey: INSPECTOR_L10N.getStr("inspectorCopyOuterHTML.accesskey"), - disabled: !isSelectionElement, - click: () => this.copyOuterHTML(), - })); - copySubmenu.append(new MenuItem({ - id: "node-menu-copyuniqueselector", - label: INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.label"), - accesskey: - INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.accesskey"), - disabled: !isSelectionElement, - click: () => this.copyUniqueSelector(), - })); - copySubmenu.append(new MenuItem({ - id: "node-menu-copycsspath", - label: INSPECTOR_L10N.getStr("inspectorCopyCSSPath.label"), - accesskey: - INSPECTOR_L10N.getStr("inspectorCopyCSSPath.accesskey"), - disabled: !isSelectionElement, - click: () => this.copyCssPath(), - })); - copySubmenu.append(new MenuItem({ - id: "node-menu-copyxpath", - label: INSPECTOR_L10N.getStr("inspectorCopyXPath.label"), - accesskey: - INSPECTOR_L10N.getStr("inspectorCopyXPath.accesskey"), - disabled: !isSelectionElement, - click: () => this.copyXPath(), - })); - copySubmenu.append(new MenuItem({ - id: "node-menu-copyimagedatauri", - label: INSPECTOR_L10N.getStr("inspectorImageDataUri.label"), - disabled: !isSelectionElement || !markupContainer || - !markupContainer.isPreviewable(), - click: () => this.copyImageDataUri(), - })); - - return copySubmenu; - }, - - _getPasteSubmenu: function(isEditableElement) { - const isPasteable = isEditableElement && this._getClipboardContentForPaste(); - const disableAdjacentPaste = !isPasteable || this.selection.isRoot() || - this.selection.isBodyNode() || this.selection.isHeadNode(); - const disableFirstLastPaste = !isPasteable || - (this.selection.isHTMLNode() && this.selection.isRoot()); - - const pasteSubmenu = new Menu(); - pasteSubmenu.append(new MenuItem({ - id: "node-menu-pasteinnerhtml", - label: INSPECTOR_L10N.getStr("inspectorPasteInnerHTML.label"), - accesskey: INSPECTOR_L10N.getStr("inspectorPasteInnerHTML.accesskey"), - disabled: !isPasteable, - click: () => this.pasteInnerHTML(), - })); - pasteSubmenu.append(new MenuItem({ - id: "node-menu-pasteouterhtml", - label: INSPECTOR_L10N.getStr("inspectorPasteOuterHTML.label"), - accesskey: INSPECTOR_L10N.getStr("inspectorPasteOuterHTML.accesskey"), - disabled: !isPasteable, - click: () => this.pasteOuterHTML(), - })); - pasteSubmenu.append(new MenuItem({ - id: "node-menu-pastebefore", - label: INSPECTOR_L10N.getStr("inspectorHTMLPasteBefore.label"), - accesskey: - INSPECTOR_L10N.getStr("inspectorHTMLPasteBefore.accesskey"), - disabled: disableAdjacentPaste, - click: () => this.pasteAdjacentHTML("beforeBegin"), - })); - pasteSubmenu.append(new MenuItem({ - id: "node-menu-pasteafter", - label: INSPECTOR_L10N.getStr("inspectorHTMLPasteAfter.label"), - accesskey: - INSPECTOR_L10N.getStr("inspectorHTMLPasteAfter.accesskey"), - disabled: disableAdjacentPaste, - click: () => this.pasteAdjacentHTML("afterEnd"), - })); - pasteSubmenu.append(new MenuItem({ - id: "node-menu-pastefirstchild", - label: INSPECTOR_L10N.getStr("inspectorHTMLPasteFirstChild.label"), - accesskey: - INSPECTOR_L10N.getStr("inspectorHTMLPasteFirstChild.accesskey"), - disabled: disableFirstLastPaste, - click: () => this.pasteAdjacentHTML("afterBegin"), - })); - pasteSubmenu.append(new MenuItem({ - id: "node-menu-pastelastchild", - label: INSPECTOR_L10N.getStr("inspectorHTMLPasteLastChild.label"), - accesskey: - INSPECTOR_L10N.getStr("inspectorHTMLPasteLastChild.accesskey"), - disabled: disableFirstLastPaste, - click: () => this.pasteAdjacentHTML("beforeEnd"), - })); - - return pasteSubmenu; - }, - - _getAttributesSubmenu: function(isEditableElement) { - const attributesSubmenu = new Menu(); - const nodeInfo = this.nodeMenuTriggerInfo; - const isAttributeClicked = isEditableElement && nodeInfo && - nodeInfo.type === "attribute"; - - attributesSubmenu.append(new MenuItem({ - id: "node-menu-add-attribute", - label: INSPECTOR_L10N.getStr("inspectorAddAttribute.label"), - accesskey: INSPECTOR_L10N.getStr("inspectorAddAttribute.accesskey"), - disabled: !isEditableElement, - click: () => this.onAddAttribute(), - })); - attributesSubmenu.append(new MenuItem({ - id: "node-menu-copy-attribute", - label: INSPECTOR_L10N.getFormatStr("inspectorCopyAttributeValue.label", - isAttributeClicked ? `${nodeInfo.value}` : ""), - accesskey: INSPECTOR_L10N.getStr("inspectorCopyAttributeValue.accesskey"), - disabled: !isAttributeClicked, - click: () => this.onCopyAttributeValue(), - })); - attributesSubmenu.append(new MenuItem({ - id: "node-menu-edit-attribute", - label: INSPECTOR_L10N.getFormatStr("inspectorEditAttribute.label", - isAttributeClicked ? `${nodeInfo.name}` : ""), - accesskey: INSPECTOR_L10N.getStr("inspectorEditAttribute.accesskey"), - disabled: !isAttributeClicked, - click: () => this.onEditAttribute(), - })); - attributesSubmenu.append(new MenuItem({ - id: "node-menu-remove-attribute", - label: INSPECTOR_L10N.getFormatStr("inspectorRemoveAttribute.label", - isAttributeClicked ? `${nodeInfo.name}` : ""), - accesskey: INSPECTOR_L10N.getStr("inspectorRemoveAttribute.accesskey"), - disabled: !isAttributeClicked, - click: () => this.onRemoveAttribute(), - })); - - return attributesSubmenu; - }, - - /** - * Link menu items can be shown or hidden depending on the context and - * selected node, and their labels can vary. - * - * @return {Array} list of visible menu items related to links. - */ - _getNodeLinkMenuItems: function() { - const linkFollow = new MenuItem({ - id: "node-menu-link-follow", - visible: false, - click: () => this.onFollowLink(), - }); - const linkCopy = new MenuItem({ - id: "node-menu-link-copy", - visible: false, - click: () => this.onCopyLink(), - }); - - // Get information about the right-clicked node. - const popupNode = this.contextMenuTarget; - if (!popupNode || !popupNode.classList.contains("link")) { - return [linkFollow, linkCopy]; - } - - const type = popupNode.dataset.type; - if ((type === "uri" || type === "cssresource" || type === "jsresource")) { - // Links can't be opened in new tabs in the browser toolbox. - if (type === "uri" && !this.target.chrome) { - linkFollow.visible = true; - linkFollow.label = INSPECTOR_L10N.getStr( - "inspector.menu.openUrlInNewTab.label"); - } else if (type === "cssresource") { - linkFollow.visible = true; - linkFollow.label = TOOLBOX_L10N.getStr( - "toolbox.viewCssSourceInStyleEditor.label"); - } else if (type === "jsresource") { - linkFollow.visible = true; - linkFollow.label = TOOLBOX_L10N.getStr( - "toolbox.viewJsSourceInDebugger.label"); - } - - linkCopy.visible = true; - linkCopy.label = INSPECTOR_L10N.getStr( - "inspector.menu.copyUrlToClipboard.label"); - } else if (type === "idref") { - linkFollow.visible = true; - linkFollow.label = INSPECTOR_L10N.getFormatStr( - "inspector.menu.selectElement.label", popupNode.dataset.link); - } - - return [linkFollow, linkCopy]; - }, - _initMarkup: function() { if (!this._markupFrame) { this._markupFrame = this.panelDoc.createElement("iframe"); @@ -1913,7 +1457,6 @@ Inspector.prototype = { _onMarkupFrameLoad: function() { this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true); - this._markupFrame.contentWindow.addEventListener("contextmenu", this._onContextMenu); this._markupFrame.contentWindow.focus(); this._markupBox.style.visibility = "visible"; this.markup = new MarkupView(this, this._markupFrame, this._toolbox.win); @@ -2028,241 +1571,6 @@ Inspector.prototype = { return promise.resolve(); }, - /** - * Show DOM properties - */ - showDOMProperties: function() { - this._toolbox.openSplitConsole().then(() => { - const panel = this._toolbox.getPanel("webconsole"); - const jsterm = panel.hud.jsterm; - - jsterm.execute("inspect($0)"); - jsterm.focus(); - }); - }, - - jumpToCustomElementDefinition: function() { - const node = this.selection.nodeFront; - const { url, line } = node.customElementLocation; - this._toolbox.viewSourceInDebugger(url, line, "show_custom_element"); - }, - - /** - * Show Accessibility properties for currently selected node - */ - async showAccessibilityProperties() { - const a11yPanel = await this._toolbox.selectTool("accessibility"); - // Select the accessible object in the panel and wait for the event that - // tells us it has been done. - const onSelected = a11yPanel.once("new-accessible-front-selected"); - a11yPanel.selectAccessibleForNode(this.selection.nodeFront, - "inspector-context-menu"); - await onSelected; - }, - - /** - * Use in Console. - * - * Takes the currently selected node in the inspector and assigns it to a - * temp variable on the content window. Also opens the split console and - * autofills it with the temp variable. - */ - useInConsole: function() { - this._toolbox.openSplitConsole().then(() => { - const panel = this._toolbox.getPanel("webconsole"); - const jsterm = panel.hud.jsterm; - - const evalString = `{ let i = 0; - while (window.hasOwnProperty("temp" + i) && i < 1000) { - i++; - } - window["temp" + i] = $0; - "temp" + i; - }`; - - const options = { - selectedNodeActor: this.selection.nodeFront.actorID, - }; - jsterm.requestEvaluation(evalString, options).then((res) => { - jsterm.setInputValue(res.result); - this.emit("console-var-ready"); - }); - }); - }, - - /** - * Edit the outerHTML of the selected Node. - */ - editHTML: function() { - if (!this.selection.isNode()) { - return; - } - if (this.markup) { - this.markup.beginEditingOuterHTML(this.selection.nodeFront); - } - }, - - /** - * Paste the contents of the clipboard into the selected Node's outer HTML. - */ - pasteOuterHTML: function() { - const content = this._getClipboardContentForPaste(); - if (!content) { - return promise.reject("No clipboard content for paste"); - } - - const node = this.selection.nodeFront; - return this.markup.getNodeOuterHTML(node).then(oldContent => { - this.markup.updateNodeOuterHTML(node, content, oldContent); - }); - }, - - /** - * Paste the contents of the clipboard into the selected Node's inner HTML. - */ - pasteInnerHTML: function() { - const content = this._getClipboardContentForPaste(); - if (!content) { - return promise.reject("No clipboard content for paste"); - } - - const node = this.selection.nodeFront; - return this.markup.getNodeInnerHTML(node).then(oldContent => { - this.markup.updateNodeInnerHTML(node, content, oldContent); - }); - }, - - /** - * Paste the contents of the clipboard as adjacent HTML to the selected Node. - * @param position - * The position as specified for Element.insertAdjacentHTML - * (i.e. "beforeBegin", "afterBegin", "beforeEnd", "afterEnd"). - */ - pasteAdjacentHTML: function(position) { - const content = this._getClipboardContentForPaste(); - if (!content) { - return promise.reject("No clipboard content for paste"); - } - - const node = this.selection.nodeFront; - return this.markup.insertAdjacentHTMLToNode(node, position, content); - }, - - /** - * Copy the innerHTML of the selected Node to the clipboard. - */ - copyInnerHTML: function() { - if (!this.selection.isNode()) { - return; - } - this._copyLongString(this.walker.innerHTML(this.selection.nodeFront)); - }, - - /** - * Copy the outerHTML of the selected Node to the clipboard. - */ - copyOuterHTML: function() { - if (!this.selection.isNode()) { - return; - } - const node = this.selection.nodeFront; - - switch (node.nodeType) { - case nodeConstants.ELEMENT_NODE : - this._copyLongString(this.walker.outerHTML(node)); - break; - case nodeConstants.COMMENT_NODE : - this._getLongString(node.getNodeValue()).then(comment => { - clipboardHelper.copyString(""); - }); - break; - case nodeConstants.DOCUMENT_TYPE_NODE : - clipboardHelper.copyString(node.doctypeString); - break; - } - }, - - /** - * Copy the data-uri for the currently selected image in the clipboard. - */ - copyImageDataUri: function() { - const container = this.markup.getContainer(this.selection.nodeFront); - if (container && container.isPreviewable()) { - container.copyImageDataUri(); - } - }, - - /** - * Copy the content of a longString (via a promise resolving a - * LongStringActor) to the clipboard - * @param {Promise} longStringActorPromise - * promise expected to resolve a LongStringActor instance - * @return {Promise} promise resolving (with no argument) when the - * string is sent to the clipboard - */ - _copyLongString: function(longStringActorPromise) { - return this._getLongString(longStringActorPromise).then(string => { - clipboardHelper.copyString(string); - }).catch(console.error); - }, - - /** - * Retrieve the content of a longString (via a promise resolving a LongStringActor) - * @param {Promise} longStringActorPromise - * promise expected to resolve a LongStringActor instance - * @return {Promise} promise resolving with the retrieved string as argument - */ - _getLongString: function(longStringActorPromise) { - return longStringActorPromise.then(longStringActor => { - return longStringActor.string().then(string => { - longStringActor.release().catch(console.error); - return string; - }); - }).catch(console.error); - }, - - /** - * Copy a unique selector of the selected Node to the clipboard. - */ - copyUniqueSelector: function() { - if (!this.selection.isNode()) { - return; - } - - this.telemetry.scalarSet("devtools.copy.unique.css.selector.opened", 1); - this.selection.nodeFront.getUniqueSelector().then(selector => { - clipboardHelper.copyString(selector); - }).catch(console.error); - }, - - /** - * Copy the full CSS Path of the selected Node to the clipboard. - */ - copyCssPath: function() { - if (!this.selection.isNode()) { - return; - } - - this.telemetry.scalarSet("devtools.copy.full.css.selector.opened", 1); - this.selection.nodeFront.getCssPath().then(path => { - clipboardHelper.copyString(path); - }).catch(console.error); - }, - - /** - * Copy the XPath of the selected Node to the clipboard. - */ - copyXPath: function() { - if (!this.selection.isNode()) { - return; - } - - this.telemetry.scalarSet("devtools.copy.xpath.opened", 1); - this.selection.nodeFront.getXPath().then(path => { - clipboardHelper.copyString(path); - }).catch(console.error); - }, - /** * Initiate screenshot command on selected node. */ @@ -2285,160 +1593,6 @@ Inspector.prototype = { await saveScreenshot(this.panelWin, args, screenshot); }, - /** - * Scroll the node into view. - */ - scrollNodeIntoView: function() { - if (!this.selection.isNode()) { - return; - } - - this.selection.nodeFront.scrollIntoView(); - }, - - /** - * Duplicate the selected node - */ - duplicateNode: function() { - const selection = this.selection; - if (!selection.isElementNode() || - selection.isRoot() || - selection.isAnonymousNode() || - selection.isPseudoElementNode()) { - return; - } - this.walker.duplicateNode(selection.nodeFront).catch(console.error); - }, - - /** - * Delete the selected node. - */ - deleteNode: function() { - if (!this.selection.isNode() || - this.selection.isRoot()) { - return; - } - - // If the markup panel is active, use the markup panel to delete - // the node, making this an undoable action. - if (this.markup) { - this.markup.deleteNode(this.selection.nodeFront); - } else { - // remove the node from content - this.walker.removeNode(this.selection.nodeFront); - } - }, - - /** - * Add attribute to node. - * Used for node context menu and shouldn't be called directly. - */ - onAddAttribute: function() { - const container = this.markup.getContainer(this.selection.nodeFront); - container.addAttribute(); - }, - - /** - * Copy attribute value for node. - * Used for node context menu and shouldn't be called directly. - */ - onCopyAttributeValue: function() { - clipboardHelper.copyString(this.nodeMenuTriggerInfo.value); - }, - - /** - * Edit attribute for node. - * Used for node context menu and shouldn't be called directly. - */ - onEditAttribute: function() { - const container = this.markup.getContainer(this.selection.nodeFront); - container.editAttribute(this.nodeMenuTriggerInfo.name); - }, - - /** - * Remove attribute from node. - * Used for node context menu and shouldn't be called directly. - */ - onRemoveAttribute: function() { - const container = this.markup.getContainer(this.selection.nodeFront); - container.removeAttribute(this.nodeMenuTriggerInfo.name); - }, - - expandNode: function() { - this.markup.expandAll(this.selection.nodeFront); - }, - - collapseAll: function() { - this.markup.collapseAll(this.selection.nodeFront); - }, - - /** - * This method is here for the benefit of the node-menu-link-follow menu item - * in the inspector contextual-menu. - */ - onFollowLink: function() { - const type = this.contextMenuTarget.dataset.type; - const link = this.contextMenuTarget.dataset.link; - - this.followAttributeLink(type, link); - }, - - /** - * Given a type and link found in a node's attribute in the markup-view, - * attempt to follow that link (which may result in opening a new tab, the - * style editor or debugger). - */ - followAttributeLink: function(type, link) { - if (!type || !link) { - return; - } - - if (type === "uri" || type === "cssresource" || type === "jsresource") { - // Open link in a new tab. - this.inspector.resolveRelativeURL( - link, this.selection.nodeFront).then(url => { - if (type === "uri") { - openContentLink(url); - } else if (type === "cssresource") { - return this.toolbox.viewSourceInStyleEditor(url); - } else if (type === "jsresource") { - return this.toolbox.viewSourceInDebugger(url); - } - return null; - }).catch(console.error); - } else if (type == "idref") { - // Select the node in the same document. - this.walker.document(this.selection.nodeFront).then(doc => { - return this.walker.querySelector(doc, "#" + CSS.escape(link)).then(node => { - if (!node) { - this.emit("idref-attribute-link-failed"); - return; - } - this.selection.setNodeFront(node); - }); - }).catch(console.error); - } - }, - - /** - * This method is here for the benefit of the node-menu-link-copy menu item - * in the inspector contextual-menu. - */ - onCopyLink: function() { - const link = this.contextMenuTarget.dataset.link; - - this.copyAttributeLink(link); - }, - - /** - * This method is here for the benefit of copying links. - */ - copyAttributeLink: function(link) { - this.inspector.resolveRelativeURL(link, this.selection.nodeFront).then(url => { - clipboardHelper.copyString(url); - }, console.error); - }, - /** * Returns an object containing the shared handler functions used in the box * model and grid React components. @@ -2464,18 +1618,6 @@ Inspector.prototype = { toolbox.highlighter.highlight(nodeFront, options); }, - /** - * Returns a value indicating whether a node can be deleted. - * - * @param {NodeFront} nodeFront - * The node to test for deletion - */ - isDeletable(nodeFront) { - return !(nodeFront.isDocumentElement || - nodeFront.nodeType == nodeConstants.DOCUMENT_TYPE_NODE || - nodeFront.isAnonymous); - }, - async inspectNodeActor(nodeActor, inspectFromAnnotation) { const nodeFront = await this.walker.gripToNodeFront({ actor: nodeActor }); if (!nodeFront) { diff --git a/devtools/client/inspector/markup/markup-context-menu.js b/devtools/client/inspector/markup/markup-context-menu.js new file mode 100644 index 000000000000..30f3e9c2a672 --- /dev/null +++ b/devtools/client/inspector/markup/markup-context-menu.js @@ -0,0 +1,776 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Services = require("Services"); +const promise = require("promise"); +const { LocalizationHelper } = require("devtools/shared/l10n"); + +loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu"); +loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item"); +loader.lazyRequireGetter(this, "copyLongString", "devtools/client/inspector/shared/utils", true); +loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard"); + +loader.lazyGetter(this, "TOOLBOX_L10N", function() { + return new LocalizationHelper("devtools/client/locales/toolbox.properties"); +}); + +const INSPECTOR_L10N = + new LocalizationHelper("devtools/client/locales/inspector.properties"); + +/** + * Context menu for the Markup view. + */ +class MarkupContextMenu { + constructor(markup) { + this.markup = markup; + this.inspector = markup.inspector; + this.selection = this.inspector.selection; + this.target = this.inspector.target; + this.telemetry = this.inspector.telemetry; + this.toolbox = this.inspector.toolbox; + this.walker = this.inspector.walker; + } + + show(event) { + if (!(event.originalTarget instanceof Element) || + event.originalTarget.closest("input[type=text]") || + event.originalTarget.closest("input:not([type])") || + event.originalTarget.closest("textarea")) { + return; + } + + event.stopPropagation(); + event.preventDefault(); + + this._openMenu({ + screenX: event.screenX, + screenY: event.screenY, + target: event.target, + }); + } + + /** + * This method is here for the benefit of copying links. + */ + _copyAttributeLink(link) { + this.inspector.resolveRelativeURL(link, this.selection.nodeFront).then(url => { + clipboardHelper.copyString(url); + }, console.error); + } + + /** + * Copy the full CSS Path of the selected Node to the clipboard. + */ + _copyCssPath() { + if (!this.selection.isNode()) { + return; + } + + this.telemetry.scalarSet("devtools.copy.full.css.selector.opened", 1); + this.selection.nodeFront.getCssPath().then(path => { + clipboardHelper.copyString(path); + }).catch(console.error); + } + + /** + * Copy the data-uri for the currently selected image in the clipboard. + */ + _copyImageDataUri() { + const container = this.markup.getContainer(this.selection.nodeFront); + if (container && container.isPreviewable()) { + container.copyImageDataUri(); + } + } + + /** + * Copy the innerHTML of the selected Node to the clipboard. + */ + _copyInnerHTML() { + if (!this.selection.isNode()) { + return; + } + + copyLongString(this.walker.innerHTML(this.selection.nodeFront)); + } + + /** + * Copy the outerHTML of the selected Node to the clipboard. + */ + _copyOuterHTML() { + if (!this.selection.isNode()) { + return; + } + + this.markup.copyOuterHTML(); + } + + /** + * Copy a unique selector of the selected Node to the clipboard. + */ + _copyUniqueSelector() { + if (!this.selection.isNode()) { + return; + } + + this.telemetry.scalarSet("devtools.copy.unique.css.selector.opened", 1); + this.selection.nodeFront.getUniqueSelector().then(selector => { + clipboardHelper.copyString(selector); + }).catch(console.error); + } + + /** + * Copy the XPath of the selected Node to the clipboard. + */ + _copyXPath() { + if (!this.selection.isNode()) { + return; + } + + this.telemetry.scalarSet("devtools.copy.xpath.opened", 1); + this.selection.nodeFront.getXPath().then(path => { + clipboardHelper.copyString(path); + }).catch(console.error); + } + + /** + * Delete the selected node. + */ + _deleteNode() { + if (!this.selection.isNode() || + this.selection.isRoot()) { + return; + } + + // If the markup panel is active, use the markup panel to delete + // the node, making this an undoable action. + if (this.markup) { + this.markup.deleteNode(this.selection.nodeFront); + } else { + // remove the node from content + this.walker.removeNode(this.selection.nodeFront); + } + } + + /** + * Duplicate the selected node + */ + _duplicateNode() { + if (!this.selection.isElementNode() || + this.selection.isRoot() || + this.selection.isAnonymousNode() || + this.selection.isPseudoElementNode()) { + return; + } + + this.walker.duplicateNode(this.selection.nodeFront).catch(console.error); + } + + /** + * Edit the outerHTML of the selected Node. + */ + _editHTML() { + if (!this.selection.isNode()) { + return; + } + + this.markup.beginEditingOuterHTML(this.selection.nodeFront); + } + + /** + * Jumps to the custom element definition in the debugger. + */ + _jumpToCustomElementDefinition() { + const { url, line } = this.selection.nodeFront.customElementLocation; + this.toolbox.viewSourceInDebugger(url, line, "show_custom_element"); + } + + /** + * Add attribute to node. + * Used for node context menu and shouldn't be called directly. + */ + _onAddAttribute() { + const container = this.markup.getContainer(this.selection.nodeFront); + container.addAttribute(); + } + + /** + * Copy attribute value for node. + * Used for node context menu and shouldn't be called directly. + */ + _onCopyAttributeValue() { + clipboardHelper.copyString(this.nodeMenuTriggerInfo.value); + } + + /** + * This method is here for the benefit of the node-menu-link-copy menu item + * in the inspector contextual-menu. + */ + _onCopyLink() { + this.copyAttributeLink(this.contextMenuTarget.dataset.link); + } + + /** + * Edit attribute for node. + * Used for node context menu and shouldn't be called directly. + */ + _onEditAttribute() { + const container = this.markup.getContainer(this.selection.nodeFront); + container.editAttribute(this.nodeMenuTriggerInfo.name); + } + + /** + * This method is here for the benefit of the node-menu-link-follow menu item + * in the inspector contextual-menu. + */ + _onFollowLink() { + const type = this.contextMenuTarget.dataset.type; + const link = this.contextMenuTarget.dataset.link; + this.markup.followAttributeLink(type, link); + } + + /** + * Remove attribute from node. + * Used for node context menu and shouldn't be called directly. + */ + _onRemoveAttribute() { + const container = this.markup.getContainer(this.selection.nodeFront); + container.removeAttribute(this.nodeMenuTriggerInfo.name); + } + + /** + * Paste the contents of the clipboard as adjacent HTML to the selected Node. + * + * @param {String} position + * The position as specified for Element.insertAdjacentHTML + * (i.e. "beforeBegin", "afterBegin", "beforeEnd", "afterEnd"). + */ + _pasteAdjacentHTML(position) { + const content = this._getClipboardContentForPaste(); + if (!content) { + return promise.reject("No clipboard content for paste"); + } + + const node = this.selection.nodeFront; + return this.markup.insertAdjacentHTMLToNode(node, position, content); + } + + /** + * Paste the contents of the clipboard into the selected Node's inner HTML. + */ + _pasteInnerHTML() { + const content = this._getClipboardContentForPaste(); + if (!content) { + return promise.reject("No clipboard content for paste"); + } + + const node = this.selection.nodeFront; + return this.markup.getNodeInnerHTML(node).then(oldContent => { + this.markup.updateNodeInnerHTML(node, content, oldContent); + }); + } + + /** + * Paste the contents of the clipboard into the selected Node's outer HTML. + */ + _pasteOuterHTML() { + const content = this._getClipboardContentForPaste(); + if (!content) { + return promise.reject("No clipboard content for paste"); + } + + const node = this.selection.nodeFront; + return this.markup.getNodeOuterHTML(node).then(oldContent => { + this.markup.updateNodeOuterHTML(node, content, oldContent); + }); + } + + /** + * Show Accessibility properties for currently selected node + */ + async _showAccessibilityProperties() { + const a11yPanel = await this.toolbox.selectTool("accessibility"); + // Select the accessible object in the panel and wait for the event that + // tells us it has been done. + const onSelected = a11yPanel.once("new-accessible-front-selected"); + a11yPanel.selectAccessibleForNode(this.selection.nodeFront, "inspector-context-menu"); + await onSelected; + } + + /** + * Show DOM properties + */ + _showDOMProperties() { + this.toolbox.openSplitConsole().then(() => { + const panel = this.toolbox.getPanel("webconsole"); + const jsterm = panel.hud.jsterm; + + jsterm.execute("inspect($0)"); + jsterm.focus(); + }); + } + + /** + * Use in Console. + * + * Takes the currently selected node in the inspector and assigns it to a + * temp variable on the content window. Also opens the split console and + * autofills it with the temp variable. + */ + _useInConsole() { + this.toolbox.openSplitConsole().then(() => { + const panel = this.toolbox.getPanel("webconsole"); + const jsterm = panel.hud.jsterm; + + const evalString = `{ let i = 0; + while (window.hasOwnProperty("temp" + i) && i < 1000) { + i++; + } + window["temp" + i] = $0; + "temp" + i; + }`; + + const options = { + selectedNodeActor: this.selection.nodeFront.actorID, + }; + jsterm.requestEvaluation(evalString, options).then((res) => { + jsterm.setInputValue(res.result); + this.inspector.emit("console-var-ready"); + }); + }); + } + + _buildA11YMenuItem(menu) { + if (!(this.selection.isElementNode() || this.selection.isTextNode()) || + !Services.prefs.getBoolPref("devtools.accessibility.enabled")) { + return; + } + + const showA11YPropsItem = new MenuItem({ + id: "node-menu-showaccessibilityproperties", + label: INSPECTOR_L10N.getStr("inspectorShowAccessibilityProperties.label"), + click: () => this._showAccessibilityProperties(), + disabled: true, + }); + + // Only attempt to determine if a11y props menu item needs to be enabled if + // AccessibilityFront is enabled. + if (this.inspector.accessibilityFront.enabled) { + this._updateA11YMenuItem(showA11YPropsItem); + } + + menu.append(showA11YPropsItem); + } + + _getAttributesSubmenu(isEditableElement) { + const attributesSubmenu = new Menu(); + const nodeInfo = this.nodeMenuTriggerInfo; + const isAttributeClicked = isEditableElement && nodeInfo && + nodeInfo.type === "attribute"; + + attributesSubmenu.append(new MenuItem({ + id: "node-menu-add-attribute", + label: INSPECTOR_L10N.getStr("inspectorAddAttribute.label"), + accesskey: INSPECTOR_L10N.getStr("inspectorAddAttribute.accesskey"), + disabled: !isEditableElement, + click: () => this._onAddAttribute(), + })); + attributesSubmenu.append(new MenuItem({ + id: "node-menu-copy-attribute", + label: INSPECTOR_L10N.getFormatStr("inspectorCopyAttributeValue.label", + isAttributeClicked ? `${nodeInfo.value}` : ""), + accesskey: INSPECTOR_L10N.getStr("inspectorCopyAttributeValue.accesskey"), + disabled: !isAttributeClicked, + click: () => this._onCopyAttributeValue(), + })); + attributesSubmenu.append(new MenuItem({ + id: "node-menu-edit-attribute", + label: INSPECTOR_L10N.getFormatStr("inspectorEditAttribute.label", + isAttributeClicked ? `${nodeInfo.name}` : ""), + accesskey: INSPECTOR_L10N.getStr("inspectorEditAttribute.accesskey"), + disabled: !isAttributeClicked, + click: () => this._onEditAttribute(), + })); + attributesSubmenu.append(new MenuItem({ + id: "node-menu-remove-attribute", + label: INSPECTOR_L10N.getFormatStr("inspectorRemoveAttribute.label", + isAttributeClicked ? `${nodeInfo.name}` : ""), + accesskey: INSPECTOR_L10N.getStr("inspectorRemoveAttribute.accesskey"), + disabled: !isAttributeClicked, + click: () => this._onRemoveAttribute(), + })); + + return attributesSubmenu; + } + + /** + * Returns the clipboard content if it is appropriate for pasting + * into the current node's outer HTML, otherwise returns null. + */ + _getClipboardContentForPaste() { + const content = clipboardHelper.getText(); + if (content && content.trim().length > 0) { + return content; + } + return null; + } + + _getCopySubmenu(markupContainer, isSelectionElement) { + const copySubmenu = new Menu(); + copySubmenu.append(new MenuItem({ + id: "node-menu-copyinner", + label: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.label"), + accesskey: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.accesskey"), + disabled: !isSelectionElement, + click: () => this._copyInnerHTML(), + })); + copySubmenu.append(new MenuItem({ + id: "node-menu-copyouter", + label: INSPECTOR_L10N.getStr("inspectorCopyOuterHTML.label"), + accesskey: INSPECTOR_L10N.getStr("inspectorCopyOuterHTML.accesskey"), + disabled: !isSelectionElement, + click: () => this._copyOuterHTML(), + })); + copySubmenu.append(new MenuItem({ + id: "node-menu-copyuniqueselector", + label: INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.label"), + accesskey: + INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.accesskey"), + disabled: !isSelectionElement, + click: () => this._copyUniqueSelector(), + })); + copySubmenu.append(new MenuItem({ + id: "node-menu-copycsspath", + label: INSPECTOR_L10N.getStr("inspectorCopyCSSPath.label"), + accesskey: + INSPECTOR_L10N.getStr("inspectorCopyCSSPath.accesskey"), + disabled: !isSelectionElement, + click: () => this._copyCssPath(), + })); + copySubmenu.append(new MenuItem({ + id: "node-menu-copyxpath", + label: INSPECTOR_L10N.getStr("inspectorCopyXPath.label"), + accesskey: + INSPECTOR_L10N.getStr("inspectorCopyXPath.accesskey"), + disabled: !isSelectionElement, + click: () => this._copyXPath(), + })); + copySubmenu.append(new MenuItem({ + id: "node-menu-copyimagedatauri", + label: INSPECTOR_L10N.getStr("inspectorImageDataUri.label"), + disabled: !isSelectionElement || !markupContainer || + !markupContainer.isPreviewable(), + click: () => this._copyImageDataUri(), + })); + + return copySubmenu; + } + + /** + * Link menu items can be shown or hidden depending on the context and + * selected node, and their labels can vary. + * + * @return {Array} list of visible menu items related to links. + */ + _getNodeLinkMenuItems() { + const linkFollow = new MenuItem({ + id: "node-menu-link-follow", + visible: false, + click: () => this._onFollowLink(), + }); + const linkCopy = new MenuItem({ + id: "node-menu-link-copy", + visible: false, + click: () => this._onCopyLink(), + }); + + // Get information about the right-clicked node. + const popupNode = this.contextMenuTarget; + if (!popupNode || !popupNode.classList.contains("link")) { + return [linkFollow, linkCopy]; + } + + const type = popupNode.dataset.type; + if ((type === "uri" || type === "cssresource" || type === "jsresource")) { + // Links can't be opened in new tabs in the browser toolbox. + if (type === "uri" && !this.target.chrome) { + linkFollow.visible = true; + linkFollow.label = INSPECTOR_L10N.getStr( + "inspector.menu.openUrlInNewTab.label"); + } else if (type === "cssresource") { + linkFollow.visible = true; + linkFollow.label = TOOLBOX_L10N.getStr( + "toolbox.viewCssSourceInStyleEditor.label"); + } else if (type === "jsresource") { + linkFollow.visible = true; + linkFollow.label = TOOLBOX_L10N.getStr( + "toolbox.viewJsSourceInDebugger.label"); + } + + linkCopy.visible = true; + linkCopy.label = INSPECTOR_L10N.getStr( + "inspector.menu.copyUrlToClipboard.label"); + } else if (type === "idref") { + linkFollow.visible = true; + linkFollow.label = INSPECTOR_L10N.getFormatStr( + "inspector.menu.selectElement.label", popupNode.dataset.link); + } + + return [linkFollow, linkCopy]; + } + + _getPasteSubmenu(isEditableElement) { + const isPasteable = isEditableElement && this._getClipboardContentForPaste(); + const disableAdjacentPaste = !isPasteable || + this.selection.isRoot() || + this.selection.isBodyNode() || + this.selection.isHeadNode(); + const disableFirstLastPaste = !isPasteable || + (this.selection.isHTMLNode() && this.selection.isRoot()); + + const pasteSubmenu = new Menu(); + pasteSubmenu.append(new MenuItem({ + id: "node-menu-pasteinnerhtml", + label: INSPECTOR_L10N.getStr("inspectorPasteInnerHTML.label"), + accesskey: INSPECTOR_L10N.getStr("inspectorPasteInnerHTML.accesskey"), + disabled: !isPasteable, + click: () => this._pasteInnerHTML(), + })); + pasteSubmenu.append(new MenuItem({ + id: "node-menu-pasteouterhtml", + label: INSPECTOR_L10N.getStr("inspectorPasteOuterHTML.label"), + accesskey: INSPECTOR_L10N.getStr("inspectorPasteOuterHTML.accesskey"), + disabled: !isPasteable, + click: () => this._pasteOuterHTML(), + })); + pasteSubmenu.append(new MenuItem({ + id: "node-menu-pastebefore", + label: INSPECTOR_L10N.getStr("inspectorHTMLPasteBefore.label"), + accesskey: + INSPECTOR_L10N.getStr("inspectorHTMLPasteBefore.accesskey"), + disabled: disableAdjacentPaste, + click: () => this._pasteAdjacentHTML("beforeBegin"), + })); + pasteSubmenu.append(new MenuItem({ + id: "node-menu-pasteafter", + label: INSPECTOR_L10N.getStr("inspectorHTMLPasteAfter.label"), + accesskey: + INSPECTOR_L10N.getStr("inspectorHTMLPasteAfter.accesskey"), + disabled: disableAdjacentPaste, + click: () => this._pasteAdjacentHTML("afterEnd"), + })); + pasteSubmenu.append(new MenuItem({ + id: "node-menu-pastefirstchild", + label: INSPECTOR_L10N.getStr("inspectorHTMLPasteFirstChild.label"), + accesskey: + INSPECTOR_L10N.getStr("inspectorHTMLPasteFirstChild.accesskey"), + disabled: disableFirstLastPaste, + click: () => this._pasteAdjacentHTML("afterBegin"), + })); + pasteSubmenu.append(new MenuItem({ + id: "node-menu-pastelastchild", + label: INSPECTOR_L10N.getStr("inspectorHTMLPasteLastChild.label"), + accesskey: + INSPECTOR_L10N.getStr("inspectorHTMLPasteLastChild.accesskey"), + disabled: disableFirstLastPaste, + click: () => this._pasteAdjacentHTML("beforeEnd"), + })); + + return pasteSubmenu; + } + + _openMenu({ target, screenX = 0, screenY = 0 } = {}) { + if (this.selection.isSlotted()) { + // Slotted elements should not show any context menu. + return null; + } + + const markupContainer = this.markup.getContainer(this.selection.nodeFront); + + this.contextMenuTarget = target; + this.nodeMenuTriggerInfo = markupContainer && + markupContainer.editor.getInfoAtNode(target); + + const isSelectionElement = this.selection.isElementNode() && + !this.selection.isPseudoElementNode(); + const isEditableElement = isSelectionElement && + !this.selection.isAnonymousNode(); + const isDuplicatableElement = isSelectionElement && + !this.selection.isAnonymousNode() && + !this.selection.isRoot(); + const isScreenshotable = isSelectionElement && + this.selection.nodeFront.isTreeDisplayed; + + const menu = new Menu(); + menu.append(new MenuItem({ + id: "node-menu-edithtml", + label: INSPECTOR_L10N.getStr("inspectorHTMLEdit.label"), + accesskey: INSPECTOR_L10N.getStr("inspectorHTMLEdit.accesskey"), + disabled: !isEditableElement, + click: () => this._editHTML(), + })); + menu.append(new MenuItem({ + id: "node-menu-add", + label: INSPECTOR_L10N.getStr("inspectorAddNode.label"), + accesskey: INSPECTOR_L10N.getStr("inspectorAddNode.accesskey"), + disabled: !this.inspector.canAddHTMLChild(), + click: () => this.inspector.addNode(), + })); + menu.append(new MenuItem({ + id: "node-menu-duplicatenode", + label: INSPECTOR_L10N.getStr("inspectorDuplicateNode.label"), + disabled: !isDuplicatableElement, + click: () => this._duplicateNode(), + })); + menu.append(new MenuItem({ + id: "node-menu-delete", + label: INSPECTOR_L10N.getStr("inspectorHTMLDelete.label"), + accesskey: INSPECTOR_L10N.getStr("inspectorHTMLDelete.accesskey"), + disabled: !this.markup.isDeletable(this.selection.nodeFront), + click: () => this._deleteNode(), + })); + + menu.append(new MenuItem({ + label: INSPECTOR_L10N.getStr("inspectorAttributesSubmenu.label"), + accesskey: + INSPECTOR_L10N.getStr("inspectorAttributesSubmenu.accesskey"), + submenu: this._getAttributesSubmenu(isEditableElement), + })); + + menu.append(new MenuItem({ + type: "separator", + })); + + // Set the pseudo classes + for (const name of ["hover", "active", "focus", "focus-within"]) { + const menuitem = new MenuItem({ + id: "node-menu-pseudo-" + name, + label: name, + type: "checkbox", + click: () => this.inspector.togglePseudoClass(":" + name), + }); + + if (isSelectionElement) { + const checked = this.selection.nodeFront.hasPseudoClassLock(":" + name); + menuitem.checked = checked; + } else { + menuitem.disabled = true; + } + + menu.append(menuitem); + } + + menu.append(new MenuItem({ + type: "separator", + })); + + menu.append(new MenuItem({ + label: INSPECTOR_L10N.getStr("inspectorCopyHTMLSubmenu.label"), + submenu: this._getCopySubmenu(markupContainer, isSelectionElement), + })); + + menu.append(new MenuItem({ + label: INSPECTOR_L10N.getStr("inspectorPasteHTMLSubmenu.label"), + submenu: this._getPasteSubmenu(isEditableElement), + })); + + menu.append(new MenuItem({ + type: "separator", + })); + + const isNodeWithChildren = this.selection.isNode() && + markupContainer.hasChildren; + menu.append(new MenuItem({ + id: "node-menu-expand", + label: INSPECTOR_L10N.getStr("inspectorExpandNode.label"), + disabled: !isNodeWithChildren, + click: () => this.markup.expandAll(this.selection.nodeFront), + })); + menu.append(new MenuItem({ + id: "node-menu-collapse", + label: INSPECTOR_L10N.getStr("inspectorCollapseAll.label"), + disabled: !isNodeWithChildren || !markupContainer.expanded, + click: () => this.markup.collapseAll(this.selection.nodeFront), + })); + + menu.append(new MenuItem({ + type: "separator", + })); + + menu.append(new MenuItem({ + id: "node-menu-scrollnodeintoview", + label: INSPECTOR_L10N.getStr("inspectorScrollNodeIntoView.label"), + accesskey: + INSPECTOR_L10N.getStr("inspectorScrollNodeIntoView.accesskey"), + disabled: !isSelectionElement, + click: () => this.markup.scrollNodeIntoView(), + })); + menu.append(new MenuItem({ + id: "node-menu-screenshotnode", + label: INSPECTOR_L10N.getStr("inspectorScreenshotNode.label"), + disabled: !isScreenshotable, + click: () => this.inspector.screenshotNode().catch(console.error), + })); + menu.append(new MenuItem({ + id: "node-menu-useinconsole", + label: INSPECTOR_L10N.getStr("inspectorUseInConsole.label"), + click: () => this._useInConsole(), + })); + menu.append(new MenuItem({ + id: "node-menu-showdomproperties", + label: INSPECTOR_L10N.getStr("inspectorShowDOMProperties.label"), + click: () => this._showDOMProperties(), + })); + + if (this.selection.nodeFront.customElementLocation) { + menu.append(new MenuItem({ + type: "separator", + })); + + menu.append(new MenuItem({ + id: "node-menu-jumptodefinition", + label: INSPECTOR_L10N.getStr("inspectorCustomElementDefinition.label"), + click: () => this._jumpToCustomElementDefinition(), + })); + } + + this._buildA11YMenuItem(menu); + + const nodeLinkMenuItems = this._getNodeLinkMenuItems(); + if (nodeLinkMenuItems.filter(item => item.visible).length > 0) { + menu.append(new MenuItem({ + id: "node-menu-link-separator", + type: "separator", + })); + } + + for (const menuitem of nodeLinkMenuItems) { + menu.append(menuitem); + } + + menu.popup(screenX, screenY, this.toolbox); + return menu; + } + + async _updateA11YMenuItem(menuItem) { + const hasMethod = await this.target.actorHasMethod("domwalker", + "hasAccessibilityProperties"); + if (!hasMethod) { + return; + } + + const hasA11YProps = await this.walker.hasAccessibilityProperties( + this.selection.nodeFront); + if (hasA11YProps) { + this.toolbox.doc.getElementById(menuItem.id).disabled = menuItem.disabled = false; + } + + this.inspector.emit("node-menu-updated"); + } +} + +module.exports = MarkupContextMenu; diff --git a/devtools/client/inspector/markup/markup.js b/devtools/client/inspector/markup/markup.js index 2ff922e1c32e..99fb8bc35c80 100644 --- a/devtools/client/inspector/markup/markup.js +++ b/devtools/client/inspector/markup/markup.js @@ -21,9 +21,14 @@ const MarkupReadOnlyContainer = require("devtools/client/inspector/markup/views/ const MarkupTextContainer = require("devtools/client/inspector/markup/views/text-container"); const RootContainer = require("devtools/client/inspector/markup/views/root-container"); +loader.lazyRequireGetter(this, "MarkupContextMenu", "devtools/client/inspector/markup/markup-context-menu"); loader.lazyRequireGetter(this, "SlottedNodeContainer", "devtools/client/inspector/markup/views/slotted-node-container"); +loader.lazyRequireGetter(this, "copyLongString", "devtools/client/inspector/shared/utils", true); +loader.lazyRequireGetter(this, "getLongString", "devtools/client/inspector/shared/utils", true); +loader.lazyRequireGetter(this, "openContentLink", "devtools/client/shared/link", true); loader.lazyRequireGetter(this, "HTMLTooltip", "devtools/client/shared/widgets/tooltip/HTMLTooltip", true); loader.lazyRequireGetter(this, "UndoStack", "devtools/client/shared/undo", true); +loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard"); const INSPECTOR_L10N = new LocalizationHelper("devtools/client/locales/inspector.properties"); @@ -98,6 +103,7 @@ function MarkupView(inspector, frame, controllerWindow) { this._isImagePreviewTarget = this._isImagePreviewTarget.bind(this); this._mutationObserver = this._mutationObserver.bind(this); this._onBlur = this._onBlur.bind(this); + this._onContextMenu = this._onContextMenu.bind(this); this._onCopy = this._onCopy.bind(this); this._onCollapseAttributesPrefChange = this._onCollapseAttributesPrefChange.bind(this); this._onWalkerNodeStatesChanged = this._onWalkerNodeStatesChanged.bind(this); @@ -113,6 +119,7 @@ function MarkupView(inspector, frame, controllerWindow) { // Listening to various events. this._elt.addEventListener("blur", this._onBlur, true); this._elt.addEventListener("click", this._onMouseClick); + this._elt.addEventListener("contextmenu", this._onContextMenu); this._elt.addEventListener("mousemove", this._onMouseMove); this._elt.addEventListener("mouseout", this._onMouseOut); this._frame.addEventListener("focus", this._onFocus); @@ -156,6 +163,14 @@ MarkupView.prototype = { _selectedContainer: null, + get contextMenu() { + if (!this._contextMenu) { + this._contextMenu = new MarkupContextMenu(this); + } + + return this._contextMenu; + }, + get eventDetailsTooltip() { if (!this._eventDetailsTooltip) { // This tooltip will be attached to the toolbox document. @@ -279,6 +294,10 @@ MarkupView.prototype = { } }, + _onContextMenu: function(event) { + this.contextMenu.show(event); + }, + /** * Executed on each mouse-move while a node is being dragged in the view. * Auto-scrolls the view to reveal nodes below the fold to drop the dragged @@ -766,12 +785,73 @@ MarkupView.prototype = { const selection = this.inspector.selection; if (selection.isNode()) { - this.inspector.copyOuterHTML(); + this.copyOuterHTML(); } evt.stopPropagation(); evt.preventDefault(); }, + /** + * Copy the outerHTML of the selected Node to the clipboard. + */ + copyOuterHTML: function() { + if (!this.inspector.selection.isNode()) { + return; + } + const node = this.inspector.selection.nodeFront; + + switch (node.nodeType) { + case nodeConstants.ELEMENT_NODE : + copyLongString(this.walker.outerHTML(node)); + break; + case nodeConstants.COMMENT_NODE : + getLongString(node.getNodeValue()).then(comment => { + clipboardHelper.copyString(""); + }); + break; + case nodeConstants.DOCUMENT_TYPE_NODE : + clipboardHelper.copyString(node.doctypeString); + break; + } + }, + + /** + * Given a type and link found in a node's attribute in the markup-view, + * attempt to follow that link (which may result in opening a new tab, the + * style editor or debugger). + */ + followAttributeLink: function(type, link) { + if (!type || !link) { + return; + } + + if (type === "uri" || type === "cssresource" || type === "jsresource") { + // Open link in a new tab. + this.inspector.inspector.resolveRelativeURL( + link, this.inspector.selection.nodeFront).then(url => { + if (type === "uri") { + openContentLink(url); + } else if (type === "cssresource") { + return this.toolbox.viewSourceInStyleEditor(url); + } else if (type === "jsresource") { + return this.toolbox.viewSourceInDebugger(url); + } + return null; + }).catch(console.error); + } else if (type == "idref") { + // Select the node in the same document. + this.walker.document(this.inspector.selection.nodeFront).then(doc => { + return this.walker.querySelector(doc, "#" + CSS.escape(link)).then(node => { + if (!node) { + this.emit("idref-attribute-link-failed"); + return; + } + this.inspector.selection.setNodeFront(node); + }); + }).catch(console.error); + } + }, + /** * Register all key shortcuts. */ @@ -820,8 +900,7 @@ MarkupView.prototype = { break; } case "markupView.scrollInto.key": { - const selection = this._selectedContainer.node; - this.inspector.scrollNodeIntoView(selection); + this.scrollNodeIntoView(); break; } // Generic keys @@ -960,6 +1039,18 @@ MarkupView.prototype = { } }, + /** + * Returns a value indicating whether a node can be deleted. + * + * @param {NodeFront} nodeFront + * The node to test for deletion + */ + isDeletable(nodeFront) { + return !(nodeFront.isDocumentElement || + nodeFront.nodeType == nodeConstants.DOCUMENT_TYPE_NODE || + nodeFront.isAnonymous); + }, + /** * Delete a node from the DOM. * This is an undoable action. @@ -970,7 +1061,7 @@ MarkupView.prototype = { * If set to true, focus the previous sibling, otherwise the next one. */ deleteNode: function(node, moveBackward) { - if (!this.inspector.isDeletable(node)) { + if (!this.isDeletable(node)) { return; } @@ -1017,6 +1108,17 @@ MarkupView.prototype = { }).catch(console.error); }, + /** + * Scroll the node into view. + */ + scrollNodeIntoView() { + if (!this.inspector.selection.isNode()) { + return; + } + + this.inspector.selection.nodeFront.scrollIntoView(); + }, + /** * If an editable item is focused, select its container. */ @@ -1952,6 +2054,7 @@ MarkupView.prototype = { this._elt.removeEventListener("blur", this._onBlur, true); this._elt.removeEventListener("click", this._onMouseClick); + this._elt.removeEventListener("contextmenu", this._onContextMenu); this._elt.removeEventListener("mousemove", this._onMouseMove); this._elt.removeEventListener("mouseout", this._onMouseOut); this._frame.removeEventListener("focus", this._onFocus); diff --git a/devtools/client/inspector/markup/moz.build b/devtools/client/inspector/markup/moz.build index 4d721cc3c41e..e56926d66ed7 100644 --- a/devtools/client/inspector/markup/moz.build +++ b/devtools/client/inspector/markup/moz.build @@ -9,6 +9,7 @@ DIRS += [ ] DevToolsModules( + 'markup-context-menu.js', 'markup.js', 'utils.js', ) diff --git a/devtools/client/inspector/markup/test/browser_markup_links_05.js b/devtools/client/inspector/markup/test/browser_markup_links_05.js index 48f1da3d4f87..d9195baa6497 100644 --- a/devtools/client/inspector/markup/test/browser_markup_links_05.js +++ b/devtools/client/inspector/markup/test/browser_markup_links_05.js @@ -23,7 +23,7 @@ add_task(async function() { info("Follow the link and wait for the new tab to open"); const onTabOpened = once(gBrowser.tabContainer, "TabOpen"); - inspector.onFollowLink(); + inspector.markup.contextMenu._onFollowLink(); const {target: tab} = await onTabOpened; await BrowserTestUtils.browserLoaded(tab.linkedBrowser); @@ -43,7 +43,7 @@ add_task(async function() { info("Follow the link and wait for the new node to be selected"); const onSelection = inspector.selection.once("new-node-front"); - inspector.onFollowLink(); + inspector.markup.contextMenu._onFollowLink(); await onSelection; ok(true, "A new node was selected"); @@ -59,8 +59,8 @@ add_task(async function() { }); info("Try to follow the link and check that no new node were selected"); - const onFailed = inspector.once("idref-attribute-link-failed"); - inspector.onFollowLink(); + const onFailed = inspector.markup.once("idref-attribute-link-failed"); + inspector.markup.contextMenu._onFollowLink(); await onFailed; ok(true, "The node selection failed"); diff --git a/devtools/client/inspector/markup/test/browser_markup_links_06.js b/devtools/client/inspector/markup/test/browser_markup_links_06.js index 483595f4f3f9..d7eea1bce204 100644 --- a/devtools/client/inspector/markup/test/browser_markup_links_06.js +++ b/devtools/client/inspector/markup/test/browser_markup_links_06.js @@ -23,7 +23,7 @@ add_task(async function() { info("Follow the link and wait for the style-editor to open"); const onStyleEditorReady = toolbox.once("styleeditor-ready"); - inspector.onFollowLink(); + inspector.markup.contextMenu._onFollowLink(); await onStyleEditorReady; // No real need to test that the editor opened on the right file here as this @@ -44,7 +44,7 @@ add_task(async function() { info("Follow the link and wait for the debugger to open"); const onDebuggerReady = toolbox.once("jsdebugger-ready"); - inspector.onFollowLink(); + inspector.markup.contextMenu._onFollowLink(); await onDebuggerReady; // No real need to test that the debugger opened on the right file here as diff --git a/devtools/client/inspector/markup/test/browser_markup_links_07.js b/devtools/client/inspector/markup/test/browser_markup_links_07.js index ffdb771925f3..868c29367759 100644 --- a/devtools/client/inspector/markup/test/browser_markup_links_07.js +++ b/devtools/client/inspector/markup/test/browser_markup_links_07.js @@ -99,7 +99,7 @@ async function followLinkWaitForNewNode(linkEl, isMetaClick, inspector) { } async function followLinkNoNewNode(linkEl, isMetaClick, inspector) { - const onFailed = inspector.once("idref-attribute-link-failed"); + const onFailed = inspector.markup.once("idref-attribute-link-failed"); performMouseDown(linkEl, isMetaClick); await onFailed; diff --git a/devtools/client/inspector/markup/test/browser_markup_shadowdom_copy_paths.js b/devtools/client/inspector/markup/test/browser_markup_shadowdom_copy_paths.js index f6326b3c4e8e..426225cf7578 100644 --- a/devtools/client/inspector/markup/test/browser_markup_shadowdom_copy_paths.js +++ b/devtools/client/inspector/markup/test/browser_markup_shadowdom_copy_paths.js @@ -45,9 +45,12 @@ add_task(async function() { await selectNode(divContainer.node, inspector); info("Check the copied values for the various copy*Path helpers"); - await waitForClipboardPromise(() => inspector.copyXPath(), '//*[@id="el1"]'); - await waitForClipboardPromise(() => inspector.copyCssPath(), "div#el1"); - await waitForClipboardPromise(() => inspector.copyUniqueSelector(), "#el1"); + await waitForClipboardPromise(() => inspector.markup.contextMenu._copyXPath(), + '//*[@id="el1"]'); + await waitForClipboardPromise(() => inspector.markup.contextMenu._copyCssPath(), + "div#el1"); + await waitForClipboardPromise(() => inspector.markup.contextMenu._copyUniqueSelector(), + "#el1"); info("Expand the div"); await expandContainer(inspector, divContainer); @@ -57,8 +60,10 @@ add_task(async function() { await selectNode(spanContainer.node, inspector); info("Check the copied values for the various copy*Path helpers"); - await waitForClipboardPromise(() => inspector.copyXPath(), "/div/span[3]"); - await waitForClipboardPromise(() => inspector.copyCssPath(), "div#el1 span"); - await waitForClipboardPromise(() => inspector.copyUniqueSelector(), + await waitForClipboardPromise(() => inspector.markup.contextMenu._copyXPath(), + "/div/span[3]"); + await waitForClipboardPromise(() => inspector.markup.contextMenu._copyCssPath(), + "div#el1 span"); + await waitForClipboardPromise(() => inspector.markup.contextMenu._copyUniqueSelector(), "#el1 > span:nth-child(3)"); }); diff --git a/devtools/client/inspector/markup/views/markup-container.js b/devtools/client/inspector/markup/views/markup-container.js index 572143bce582..1cb72d0e7b56 100644 --- a/devtools/client/inspector/markup/views/markup-container.js +++ b/devtools/client/inspector/markup/views/markup-container.js @@ -564,7 +564,7 @@ MarkupContainer.prototype = { const type = target.dataset.type; // Make container tabbable descendants not tabbable (by default). this.canFocus = false; - this.markup.inspector.followAttributeLink(type, link); + this.markup.followAttributeLink(type, link); return; } diff --git a/devtools/client/inspector/shared/utils.js b/devtools/client/inspector/shared/utils.js index c390f4de2e70..6f620bd4fd6b 100644 --- a/devtools/client/inspector/shared/utils.js +++ b/devtools/client/inspector/shared/utils.js @@ -11,6 +11,7 @@ const promise = require("promise"); loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true); loader.lazyRequireGetter(this, "getCSSLexer", "devtools/shared/css/lexer", true); loader.lazyRequireGetter(this, "parseDeclarations", "devtools/shared/css/parsing-utils", true); +loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard"); const HTML_NS = "http://www.w3.org/1999/xhtml"; @@ -82,6 +83,21 @@ function blurOnMultipleProperties(cssProperties) { }; } +/** + * Copy the content of a longString (via a promise resolving a + * LongStringActor) to the clipboard. + * + * @param {Promise} longStringActorPromise + * promise expected to resolve a LongStringActor instance + * @return {Promise} promise resolving (with no argument) when the + * string is sent to the clipboard + */ +function copyLongString(longStringActorPromise) { + return getLongString(longStringActorPromise).then(string => { + clipboardHelper.copyString(string); + }).catch(console.error); +} + /** * Create a child element with a set of attributes. * @@ -109,6 +125,22 @@ function createChild(parent, tagName, attributes = {}) { return elt; } +/** + * Retrieve the content of a longString (via a promise resolving a LongStringActor). + * + * @param {Promise} longStringActorPromise + * promise expected to resolve a LongStringActor instance + * @return {Promise} promise resolving with the retrieved string as argument + */ +function getLongString(longStringActorPromise) { + return longStringActorPromise.then(longStringActor => { + return longStringActor.string().then(string => { + longStringActor.release().catch(console.error); + return string; + }); + }).catch(console.error); +} + /** * Returns a selector of the Element Rep from the grip. This is based on the * getElements() function in our devtools-reps component for a ElementNode. @@ -196,7 +228,9 @@ function translateNodeFrontToGrip(nodeFront) { exports.advanceValidate = advanceValidate; exports.appendText = appendText; exports.blurOnMultipleProperties = blurOnMultipleProperties; +exports.copyLongString = copyLongString; exports.createChild = createChild; +exports.getLongString = getLongString; exports.getSelectorFromGrip = getSelectorFromGrip; exports.promiseWarn = promiseWarn; exports.translateNodeFrontToGrip = translateNodeFrontToGrip; diff --git a/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js b/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js index 76c2d7cff722..011c7257c582 100644 --- a/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js +++ b/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js @@ -36,7 +36,7 @@ add_task(async function() { const copyAttributeValue = getMenuItem("node-menu-copy-attribute"); info("Triggering 'Copy Attribute Value' and waiting for clipboard to copy the value"); - inspector.nodeMenuTriggerInfo = { + inspector.markup.contextMenu.nodeMenuTriggerInfo = { type: "attribute", name: "data-edit", value: "the", @@ -53,7 +53,7 @@ add_task(async function() { "23456789012345678901234567890123456789012345678901234567890123456789012" + "34567890123456789012345678901234567890123456789012345678901234567890123"; - inspector.nodeMenuTriggerInfo = { + inspector.markup.contextMenu.nodeMenuTriggerInfo = { type: "attribute", name: "data-edit", value: longAttribute, @@ -67,7 +67,7 @@ add_task(async function() { const editAttribute = getMenuItem("node-menu-edit-attribute"); info("Triggering 'Edit Attribute' and waiting for mutation to occur"); - inspector.nodeMenuTriggerInfo = { + inspector.markup.contextMenu.nodeMenuTriggerInfo = { type: "attribute", name: "data-edit", }; @@ -87,7 +87,7 @@ add_task(async function() { const removeAttribute = getMenuItem("node-menu-remove-attribute"); info("Triggering 'Remove Attribute' and waiting for mutation to occur"); - inspector.nodeMenuTriggerInfo = { + inspector.markup.contextMenu.nodeMenuTriggerInfo = { type: "attribute", name: "data-remove", }; diff --git a/devtools/client/inspector/test/browser_inspector_textbox-menu.js b/devtools/client/inspector/test/browser_inspector_textbox-menu.js index d0ea70c43709..927cab5a4a95 100644 --- a/devtools/client/inspector/test/browser_inspector_textbox-menu.js +++ b/devtools/client/inspector/test/browser_inspector_textbox-menu.js @@ -82,7 +82,7 @@ add_task(async function() { async function checkTextBox(textBox, toolbox) { let textboxContextMenu = toolbox.doc.getElementById("toolbox-menu"); - ok(!textboxContextMenu, "The menu is closed"); + ok(!textboxContextMenu, "The menu is closed"); info("Simulating context click on the textbox and expecting the menu to open"); const onContextMenu = toolbox.once("menu-open"); diff --git a/devtools/client/inspector/test/shared-head.js b/devtools/client/inspector/test/shared-head.js index 7b15b7a175df..2064328f872f 100644 --- a/devtools/client/inspector/test/shared-head.js +++ b/devtools/client/inspector/test/shared-head.js @@ -660,6 +660,6 @@ function openStyleContextMenuAndGetAllItems(view, target) { * @return An array of MenuItems */ function openContextMenuAndGetAllItems(inspector, options) { - const menu = inspector._openMenu(options); + const menu = inspector.markup.contextMenu._openMenu(options); return buildContextMenuItems(menu); } diff --git a/dom/base/test/test_bug422403-1.html b/dom/base/test/test_bug422403-1.html index 49b75dfdf289..bc4364b78d16 100644 --- a/dom/base/test/test_bug422403-1.html +++ b/dom/base/test/test_bug422403-1.html @@ -31,14 +31,14 @@ function loadFileContent(aFile, aCharset) { var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1'] .getService(SpecialPowers.Ci.nsIIOService); - var chann = ios.newChannel2(aFile, - aCharset, - baseUri, - null, // aLoadingNode - SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); + var chann = ios.newChannel(aFile, + aCharset, + baseUri, + null, // aLoadingNode + SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); var cis = SpecialPowers.Ci.nsIConverterInputStream; diff --git a/dom/base/test/test_bug422403-2.xhtml b/dom/base/test/test_bug422403-2.xhtml index 154da8944082..994540d475e7 100644 --- a/dom/base/test/test_bug422403-2.xhtml +++ b/dom/base/test/test_bug422403-2.xhtml @@ -30,14 +30,14 @@ function loadFileContent(aFile, aCharset) { var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1'] .getService(SpecialPowers.Ci.nsIIOService); - var chann = ios.newChannel2(aFile, - aCharset, - baseUri, - null, // aLoadingNode - SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); + var chann = ios.newChannel(aFile, + aCharset, + baseUri, + null, // aLoadingNode + SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); var cis = SpecialPowers.Ci.nsIConverterInputStream; diff --git a/dom/base/test/test_bug424359-1.html b/dom/base/test/test_bug424359-1.html index 3309ed71ce6d..f4a5c39bbb25 100644 --- a/dom/base/test/test_bug424359-1.html +++ b/dom/base/test/test_bug424359-1.html @@ -31,14 +31,14 @@ function loadFileContent(aFile, aCharset) { var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1'] .getService(SpecialPowers.Ci.nsIIOService); - var chann = ios.newChannel2(aFile, - aCharset, - baseUri, - null, // aLoadingNode - SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); + var chann = ios.newChannel(aFile, + aCharset, + baseUri, + null, // aLoadingNode + SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); var cis = SpecialPowers.Ci.nsIConverterInputStream; diff --git a/dom/base/test/test_bug424359-2.html b/dom/base/test/test_bug424359-2.html index c062cd15a5fc..33c79f5dde27 100644 --- a/dom/base/test/test_bug424359-2.html +++ b/dom/base/test/test_bug424359-2.html @@ -30,14 +30,14 @@ function loadFileContent(aFile, aCharset) { var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1'] .getService(SpecialPowers.Ci.nsIIOService); - var chann = ios.newChannel2(aFile, - aCharset, - baseUri, - null, // aLoadingNode - SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); + var chann = ios.newChannel(aFile, + aCharset, + baseUri, + null, // aLoadingNode + SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); var cis = SpecialPowers.Ci.nsIConverterInputStream; diff --git a/dom/base/test/test_bug498433.html b/dom/base/test/test_bug498433.html index e75cbce7857f..2bf9859b6865 100644 --- a/dom/base/test/test_bug498433.html +++ b/dom/base/test/test_bug498433.html @@ -29,14 +29,14 @@ function loadFileContent(aFile, aCharset) { var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1'] .getService(SpecialPowers.Ci.nsIIOService); - var chann = ios.newChannel2(aFile, - aCharset, - baseUri, - null, // aLoadingNode - SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); + var chann = ios.newChannel(aFile, + aCharset, + baseUri, + null, // aLoadingNode + SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); var cis = SpecialPowers.Ci.nsIConverterInputStream; diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index f07dfef663b6..f42bb98cdbc1 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -3422,6 +3422,14 @@ static MathSpace ExtractMathSpace(MDefinition* ins) { MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unknown TruncateKind"); } +static bool MonotoneAdd(int32_t lhs, int32_t rhs) { + return (lhs >= 0 && rhs >= 0) || (lhs <= 0 && rhs <= 0); +} + +static bool MonotoneSub(int32_t lhs, int32_t rhs) { + return (lhs >= 0 && rhs <= 0) || (lhs <= 0 && rhs >= 0); +} + // Extract a linear sum from ins, if possible (otherwise giving the // sum 'ins + 0'). SimpleLinearSum jit::ExtractLinearSum(MDefinition* ins, MathSpace space) { @@ -3471,7 +3479,8 @@ SimpleLinearSum jit::ExtractLinearSum(MDefinition* ins, MathSpace space) { int32_t constant; if (space == MathSpace::Modulo) { constant = uint32_t(lsum.constant) + uint32_t(rsum.constant); - } else if (!SafeAdd(lsum.constant, rsum.constant, &constant)) { + } else if (!SafeAdd(lsum.constant, rsum.constant, &constant) || + !MonotoneAdd(lsum.constant, rsum.constant)) { return SimpleLinearSum(ins, 0); } return SimpleLinearSum(lsum.term ? lsum.term : rsum.term, constant); @@ -3483,7 +3492,8 @@ SimpleLinearSum jit::ExtractLinearSum(MDefinition* ins, MathSpace space) { int32_t constant; if (space == MathSpace::Modulo) { constant = uint32_t(lsum.constant) - uint32_t(rsum.constant); - } else if (!SafeSub(lsum.constant, rsum.constant, &constant)) { + } else if (!SafeSub(lsum.constant, rsum.constant, &constant) || + !MonotoneSub(lsum.constant, rsum.constant)) { return SimpleLinearSum(ins, 0); } return SimpleLinearSum(lsum.term, constant); diff --git a/media/mtransport/ipc/WebrtcProxyChannel.cpp b/media/mtransport/ipc/WebrtcProxyChannel.cpp index 2b5ee71c25c0..4d92d6452b08 100644 --- a/media/mtransport/ipc/WebrtcProxyChannel.cpp +++ b/media/mtransport/ipc/WebrtcProxyChannel.cpp @@ -169,7 +169,7 @@ nsresult WebrtcProxyChannel::Open(const nsCString& aHost, const int& aPort, // -the previous proxy tunnel didn't support redirects e.g. 307. don't need to // introduce new behavior. can't follow redirects on connect anyway. nsCOMPtr localChannel; - rv = ioService->NewChannelFromURIWithProxyFlags2( + rv = ioService->NewChannelFromURIWithProxyFlags( uri, nullptr, // Proxy flags are overridden by SetConnectOnly() 0, aLoadInfo->LoadingNode(), aLoadInfo->LoadingPrincipal(), diff --git a/netwerk/base/NetUtil.jsm b/netwerk/base/NetUtil.jsm index e8655a771f53..14ec35bedddf 100644 --- a/netwerk/base/NetUtil.jsm +++ b/netwerk/base/NetUtil.jsm @@ -301,12 +301,12 @@ var NetUtil = { contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER; } - return Services.io.newChannelFromURI2(uri, - loadingNode || null, - loadingPrincipal || null, - triggeringPrincipal || null, - securityFlags, - contentPolicyType); + return Services.io.newChannelFromURI(uri, + loadingNode || null, + loadingPrincipal || null, + triggeringPrincipal || null, + securityFlags, + contentPolicyType); }, /** diff --git a/netwerk/base/nsIIOService.idl b/netwerk/base/nsIIOService.idl index 305444238d1c..cf600d00d39a 100644 --- a/netwerk/base/nsIIOService.idl +++ b/netwerk/base/nsIIOService.idl @@ -101,12 +101,12 @@ interface nsIIOService : nsISupports * Keep in mind that URIs coming from a webpage should *never* use the * systemPrincipal as the loadingPrincipal. */ - nsIChannel newChannelFromURI2(in nsIURI aURI, - in Node aLoadingNode, - in nsIPrincipal aLoadingPrincipal, - in nsIPrincipal aTriggeringPrincipal, - in unsigned long aSecurityFlags, - in unsigned long aContentPolicyType); + nsIChannel newChannelFromURI(in nsIURI aURI, + in Node aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in unsigned long aContentPolicyType); [noscript, nostdcall, notxpcom] nsresult NewChannelFromURIWithClientAndController(in nsIURI aURI, @@ -120,22 +120,22 @@ interface nsIIOService : nsISupports out nsIChannel aResult); /** - * Equivalent to newChannelFromURI2(aURI, aLoadingNode, ...) + * Equivalent to newChannelFromURI(aURI, aLoadingNode, ...) */ nsIChannel newChannelFromURIWithLoadInfo(in nsIURI aURI, in nsILoadInfo aLoadInfo); /** - * Equivalent to newChannelFromURI2(newURI(...)) + * Equivalent to newChannelFromURI(newURI(...)) */ - nsIChannel newChannel2(in AUTF8String aSpec, - in string aOriginCharset, - in nsIURI aBaseURI, - in Node aLoadingNode, - in nsIPrincipal aLoadingPrincipal, - in nsIPrincipal aTriggeringPrincipal, - in unsigned long aSecurityFlags, - in unsigned long aContentPolicyType); + nsIChannel newChannel(in AUTF8String aSpec, + in string aOriginCharset, + in nsIURI aBaseURI, + in Node aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in unsigned long aContentPolicyType); /** * Returns true if networking is in "offline" mode. When in offline mode, @@ -226,14 +226,14 @@ interface nsIIOService : nsISupports * then loadingPrincipal must be equal to loadingNode->NodePrincipal(). * But less error prone is to just supply a loadingNode. */ - nsIChannel newChannelFromURIWithProxyFlags2(in nsIURI aURI, - in nsIURI aProxyURI, - in unsigned long aProxyFlags, - in Node aLoadingNode, - in nsIPrincipal aLoadingPrincipal, - in nsIPrincipal aTriggeringPrincipal, - in unsigned long aSecurityFlags, - in unsigned long aContentPolicyType); + nsIChannel newChannelFromURIWithProxyFlags(in nsIURI aURI, + in nsIURI aProxyURI, + in unsigned long aProxyFlags, + in Node aLoadingNode, + in nsIPrincipal aLoadingPrincipal, + in nsIPrincipal aTriggeringPrincipal, + in unsigned long aSecurityFlags, + in unsigned long aContentPolicyType); }; %{C++ diff --git a/netwerk/base/nsIOService.cpp b/netwerk/base/nsIOService.cpp index 417db87b7114..78b6ec6c32e2 100644 --- a/netwerk/base/nsIOService.cpp +++ b/netwerk/base/nsIOService.cpp @@ -836,18 +836,18 @@ nsIOService::NewFileURI(nsIFile *file, nsIURI **result) { } NS_IMETHODIMP -nsIOService::NewChannelFromURI2(nsIURI *aURI, nsINode *aLoadingNode, - nsIPrincipal *aLoadingPrincipal, - nsIPrincipal *aTriggeringPrincipal, - uint32_t aSecurityFlags, - uint32_t aContentPolicyType, - nsIChannel **result) { - return NewChannelFromURIWithProxyFlags2(aURI, - nullptr, // aProxyURI - 0, // aProxyFlags - aLoadingNode, aLoadingPrincipal, - aTriggeringPrincipal, aSecurityFlags, - aContentPolicyType, result); +nsIOService::NewChannelFromURI(nsIURI *aURI, nsINode *aLoadingNode, + nsIPrincipal *aLoadingPrincipal, + nsIPrincipal *aTriggeringPrincipal, + uint32_t aSecurityFlags, + uint32_t aContentPolicyType, + nsIChannel **result) { + return NewChannelFromURIWithProxyFlags(aURI, + nullptr, // aProxyURI + 0, // aProxyFlags + aLoadingNode, aLoadingPrincipal, + aTriggeringPrincipal, aSecurityFlags, + aContentPolicyType, result); } nsresult nsIOService::NewChannelFromURIWithClientAndController( nsIURI *aURI, nsINode *aLoadingNode, nsIPrincipal *aLoadingPrincipal, @@ -924,8 +924,8 @@ nsresult nsIOService::NewChannelFromURIWithProxyFlagsInternal( nsCOMPtr channel; nsCOMPtr pph = do_QueryInterface(handler); if (pph) { - rv = pph->NewProxiedChannel2(aURI, nullptr, aProxyFlags, aProxyURI, - aLoadInfo, getter_AddRefs(channel)); + rv = pph->NewProxiedChannel(aURI, nullptr, aProxyFlags, aProxyURI, + aLoadInfo, getter_AddRefs(channel)); } else { rv = handler->NewChannel(aURI, aLoadInfo, getter_AddRefs(channel)); } @@ -976,7 +976,7 @@ nsresult nsIOService::NewChannelFromURIWithProxyFlagsInternal( } NS_IMETHODIMP -nsIOService::NewChannelFromURIWithProxyFlags2( +nsIOService::NewChannelFromURIWithProxyFlags( nsIURI *aURI, nsIURI *aProxyURI, uint32_t aProxyFlags, nsINode *aLoadingNode, nsIPrincipal *aLoadingPrincipal, nsIPrincipal *aTriggeringPrincipal, uint32_t aSecurityFlags, @@ -989,20 +989,20 @@ nsIOService::NewChannelFromURIWithProxyFlags2( } NS_IMETHODIMP -nsIOService::NewChannel2(const nsACString &aSpec, const char *aCharset, - nsIURI *aBaseURI, nsINode *aLoadingNode, - nsIPrincipal *aLoadingPrincipal, - nsIPrincipal *aTriggeringPrincipal, - uint32_t aSecurityFlags, uint32_t aContentPolicyType, - nsIChannel **result) { +nsIOService::NewChannel(const nsACString &aSpec, const char *aCharset, + nsIURI *aBaseURI, nsINode *aLoadingNode, + nsIPrincipal *aLoadingPrincipal, + nsIPrincipal *aTriggeringPrincipal, + uint32_t aSecurityFlags, uint32_t aContentPolicyType, + nsIChannel **result) { nsresult rv; nsCOMPtr uri; rv = NewURI(aSpec, aCharset, aBaseURI, getter_AddRefs(uri)); if (NS_FAILED(rv)) return rv; - return NewChannelFromURI2(uri, aLoadingNode, aLoadingPrincipal, - aTriggeringPrincipal, aSecurityFlags, - aContentPolicyType, result); + return NewChannelFromURI(uri, aLoadingNode, aLoadingPrincipal, + aTriggeringPrincipal, aSecurityFlags, + aContentPolicyType, result); } bool nsIOService::IsLinkUp() { @@ -1763,13 +1763,13 @@ nsresult nsIOService::SpeculativeConnectInternal( // channel we create underneath - hence it's safe to use // the systemPrincipal as the loadingPrincipal for this channel. nsCOMPtr channel; - rv = NewChannelFromURI2(aURI, - nullptr, // aLoadingNode, - loadingPrincipal, - nullptr, // aTriggeringPrincipal, - nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - nsIContentPolicy::TYPE_SPECULATIVE, - getter_AddRefs(channel)); + rv = NewChannelFromURI(aURI, + nullptr, // aLoadingNode, + loadingPrincipal, + nullptr, // aTriggeringPrincipal, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_SPECULATIVE, + getter_AddRefs(channel)); NS_ENSURE_SUCCESS(rv, rv); if (aAnonymous) { diff --git a/netwerk/base/nsIProxiedProtocolHandler.idl b/netwerk/base/nsIProxiedProtocolHandler.idl index 604177c12430..b87636dcccb1 100644 --- a/netwerk/base/nsIProxiedProtocolHandler.idl +++ b/netwerk/base/nsIProxiedProtocolHandler.idl @@ -29,27 +29,8 @@ interface nsIProxiedProtocolHandler : nsIProtocolHandler * a ws:// uri. * @param aLoadInfo used to evaluate who initated the resource request. */ - nsIChannel newProxiedChannel2(in nsIURI uri, in nsIProxyInfo proxyInfo, - in unsigned long proxyResolveFlags, - in nsIURI proxyURI, - in nsILoadInfo aLoadInfo); - - /** Create a new channel with the given proxyInfo - * - * @param uri the channel uri - * @param proxyInfo any proxy information that has already been determined, - * or null if channel should later determine the proxy on its own using - * proxyResolveFlags/proxyURI - * @param proxyResolveFlags used if the proxy is later determined - * from nsIProtocolProxyService::asyncResolve - * @param proxyURI used if the proxy is later determined from - * nsIProtocolProxyService::asyncResolve with this as the proxyURI name. - * Generally this is the same as uri (or null which has the same - * effect), except in the case of websockets which wants to bootstrap - * to an http:// channel but make its proxy determination based on - * a ws:// uri. - */ nsIChannel newProxiedChannel(in nsIURI uri, in nsIProxyInfo proxyInfo, in unsigned long proxyResolveFlags, - in nsIURI proxyURI); + in nsIURI proxyURI, + in nsILoadInfo aLoadInfo); }; diff --git a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp index 9550abfc0673..e38116cc43da 100644 --- a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp +++ b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp @@ -1889,7 +1889,7 @@ static nsresult CreateHTTPProxiedChannel(nsIChannel *channel, nsIProxyInfo *pi, nsCOMPtr loadInfo; channel->GetLoadInfo(getter_AddRefs(loadInfo)); - return pph->NewProxiedChannel2(uri, pi, 0, nullptr, loadInfo, newChannel); + return pph->NewProxiedChannel(uri, pi, 0, nullptr, loadInfo, newChannel); } NS_IMETHODIMP diff --git a/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp b/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp index 33268f3125a4..28264fcd9a64 100644 --- a/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp +++ b/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp @@ -173,15 +173,15 @@ nsFtpProtocolHandler::NewURI(const nsACString &aSpec, const char *aCharset, NS_IMETHODIMP nsFtpProtocolHandler::NewChannel(nsIURI *url, nsILoadInfo *aLoadInfo, nsIChannel **result) { - return NewProxiedChannel2(url, nullptr, 0, nullptr, aLoadInfo, result); + return NewProxiedChannel(url, nullptr, 0, nullptr, aLoadInfo, result); } NS_IMETHODIMP -nsFtpProtocolHandler::NewProxiedChannel2(nsIURI *uri, nsIProxyInfo *proxyInfo, - uint32_t proxyResolveFlags, - nsIURI *proxyURI, - nsILoadInfo *aLoadInfo, - nsIChannel **result) { +nsFtpProtocolHandler::NewProxiedChannel(nsIURI *uri, nsIProxyInfo *proxyInfo, + uint32_t proxyResolveFlags, + nsIURI *proxyURI, + nsILoadInfo *aLoadInfo, + nsIChannel **result) { NS_ENSURE_ARG_POINTER(uri); RefPtr channel; if (IsNeckoChild()) @@ -204,14 +204,6 @@ nsFtpProtocolHandler::NewProxiedChannel2(nsIURI *uri, nsIProxyInfo *proxyInfo, return rv; } -NS_IMETHODIMP -nsFtpProtocolHandler::NewProxiedChannel(nsIURI *uri, nsIProxyInfo *proxyInfo, - uint32_t proxyResolveFlags, - nsIURI *proxyURI, nsIChannel **result) { - return NewProxiedChannel2(uri, proxyInfo, proxyResolveFlags, proxyURI, - nullptr /*loadinfo*/, result); -} - NS_IMETHODIMP nsFtpProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) { diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 6fd834445c5b..ba2989e364b6 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -3119,8 +3119,8 @@ nsresult nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo *pi) { nsresult rv; nsCOMPtr newChannel; - rv = gHttpHandler->NewProxiedChannel2(mURI, pi, mProxyResolveFlags, mProxyURI, - mLoadInfo, getter_AddRefs(newChannel)); + rv = gHttpHandler->NewProxiedChannel(mURI, pi, mProxyResolveFlags, mProxyURI, + mLoadInfo, getter_AddRefs(newChannel)); if (NS_FAILED(rv)) return rv; uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL; diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index 59299f02a9df..6690adde4ee8 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -2047,7 +2047,7 @@ nsHttpHandler::NewChannel(nsIURI *uri, nsILoadInfo *aLoadInfo, } } - return NewProxiedChannel2(uri, nullptr, 0, nullptr, aLoadInfo, result); + return NewProxiedChannel(uri, nullptr, 0, nullptr, aLoadInfo, result); } NS_IMETHODIMP @@ -2062,9 +2062,9 @@ nsHttpHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) { //----------------------------------------------------------------------------- NS_IMETHODIMP -nsHttpHandler::NewProxiedChannel2(nsIURI *uri, nsIProxyInfo *givenProxyInfo, - uint32_t proxyResolveFlags, nsIURI *proxyURI, - nsILoadInfo *aLoadInfo, nsIChannel **result) { +nsHttpHandler::NewProxiedChannel(nsIURI *uri, nsIProxyInfo *givenProxyInfo, + uint32_t proxyResolveFlags, nsIURI *proxyURI, + nsILoadInfo *aLoadInfo, nsIChannel **result) { RefPtr httpChannel; LOG(("nsHttpHandler::NewProxiedChannel [proxyInfo=%p]\n", givenProxyInfo)); @@ -2127,14 +2127,6 @@ nsHttpHandler::NewProxiedChannel2(nsIURI *uri, nsIProxyInfo *givenProxyInfo, return NS_OK; } -NS_IMETHODIMP -nsHttpHandler::NewProxiedChannel(nsIURI *uri, nsIProxyInfo *givenProxyInfo, - uint32_t proxyResolveFlags, nsIURI *proxyURI, - nsIChannel **result) { - return NewProxiedChannel2(uri, givenProxyInfo, proxyResolveFlags, proxyURI, - nullptr, result); -} - //----------------------------------------------------------------------------- // nsHttpHandler::nsIHttpProtocolHandler //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp index 37012a2ff9fc..8ea3f1772620 100644 --- a/netwerk/protocol/viewsource/nsViewSourceChannel.cpp +++ b/netwerk/protocol/viewsource/nsViewSourceChannel.cpp @@ -60,7 +60,7 @@ nsresult nsViewSourceChannel::Init(nsIURI *uri) { return NS_ERROR_INVALID_ARG; } - // This function is called from within nsViewSourceHandler::NewChannel2 + // This function is called from within nsViewSourceHandler::NewChannel // and sets the right loadInfo right after returning from this function. // Until then we follow the principal of least privilege and use // nullPrincipal as the loadingPrincipal and the least permissive @@ -68,7 +68,7 @@ nsresult nsViewSourceChannel::Init(nsIURI *uri) { nsCOMPtr nullPrincipal = mozilla::NullPrincipal::CreateWithoutOriginAttributes(); - rv = pService->NewChannel2( + rv = pService->NewChannel( path, nullptr, // aOriginCharset nullptr, // aCharSet diff --git a/netwerk/protocol/websocket/WebSocketChannel.cpp b/netwerk/protocol/websocket/WebSocketChannel.cpp index 4a1f72f2a432..dbc4c39001a5 100644 --- a/netwerk/protocol/websocket/WebSocketChannel.cpp +++ b/netwerk/protocol/websocket/WebSocketChannel.cpp @@ -3387,7 +3387,7 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI, const nsACString &aOrigin, // Ideally we'd call newChannelFromURIWithLoadInfo here, but that doesn't // allow setting proxy uri/flags - rv = ioService->NewChannelFromURIWithProxyFlags2( + rv = ioService->NewChannelFromURIWithProxyFlags( localURI, mURI, nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY | nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL, diff --git a/netwerk/test/unit/test_NetUtil.js b/netwerk/test/unit/test_NetUtil.js index 7df260601ec2..62d304c3704b 100644 --- a/netwerk/test/unit/test_NetUtil.js +++ b/netwerk/test/unit/test_NetUtil.js @@ -513,14 +513,14 @@ function test_newChannel_with_string() // Check that we get the same URI back from channel the IO service creates and // the channel the utility method creates. let ios = Services.io - let iosChannel = ios.newChannel2(TEST_SPEC, - null, - null, - null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - Ci.nsIContentPolicy.TYPE_OTHER); + let iosChannel = ios.newChannel(TEST_SPEC, + null, + null, + null, // aLoadingNode + Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); let NetUtilChannel = NetUtil.newChannel({ uri: TEST_SPEC, loadUsingSystemPrincipal: true @@ -537,12 +537,12 @@ function test_newChannel_with_nsIURI() // Check that we get the same URI back from channel the IO service creates and // the channel the utility method creates. let uri = NetUtil.newURI(TEST_SPEC); - let iosChannel = Services.io.newChannelFromURI2(uri, - null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - Ci.nsIContentPolicy.TYPE_OTHER); + let iosChannel = Services.io.newChannelFromURI(uri, + null, // aLoadingNode + Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); let NetUtilChannel = NetUtil.newChannel({ uri: uri, loadUsingSystemPrincipal: true @@ -556,12 +556,12 @@ function test_newChannel_with_options() { let uri = "data:text/plain,"; - let iosChannel = Services.io.newChannelFromURI2(NetUtil.newURI(uri), - null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - Ci.nsIContentPolicy.TYPE_OTHER); + let iosChannel = Services.io.newChannelFromURI(NetUtil.newURI(uri), + null, // aLoadingNode + Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); function checkEqualToIOSChannel(channel) { Assert.ok(iosChannel.URI.equals(channel.URI)); diff --git a/netwerk/test/unit/test_bug1177909.js b/netwerk/test/unit/test_bug1177909.js index 68f840040e02..c28518a04d1b 100644 --- a/netwerk/test/unit/test_bug1177909.js +++ b/netwerk/test/unit/test_bug1177909.js @@ -108,14 +108,14 @@ add_task(async function testDirectProxy() { let ioService = Cc["@mozilla.org/network/io-service;1"]. getService(Ci.nsIIOService); let chan = ioService. - newChannelFromURIWithProxyFlags2(uri, - proxyURI, - 0, - null, - Services.scriptSecurityManager.getSystemPrincipal(), - null, - Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - Ci.nsIContentPolicy.TYPE_OTHER); + newChannelFromURIWithProxyFlags(uri, + proxyURI, + 0, + null, + Services.scriptSecurityManager.getSystemPrincipal(), + null, + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); let pi = await TestProxyType(chan, 0); equal(pi, null, "Expected proxy host to be null"); @@ -137,14 +137,14 @@ add_task(async function testWebSocketProxy() { let ioService = Cc["@mozilla.org/network/io-service;1"]. getService(Ci.nsIIOService); let chan = ioService. - newChannelFromURIWithProxyFlags2(uri, - proxyURI, - proxyFlags, - null, - Services.scriptSecurityManager.getSystemPrincipal(), - null, - Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - Ci.nsIContentPolicy.TYPE_OTHER); + newChannelFromURIWithProxyFlags(uri, + proxyURI, + proxyFlags, + null, + Services.scriptSecurityManager.getSystemPrincipal(), + null, + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); let pi = await TestProxyType(chan, proxyFlags); equal(pi.host, "localhost", "Expected proxy host to be localhost"); diff --git a/security/manager/ssl/nsNSSCallbacks.cpp b/security/manager/ssl/nsNSSCallbacks.cpp index 549c87d7f59e..7e448f97033c 100644 --- a/security/manager/ssl/nsNSSCallbacks.cpp +++ b/security/manager/ssl/nsNSSCallbacks.cpp @@ -239,12 +239,12 @@ OCSPRequest::Run() { } nsCOMPtr channel; - rv = ios->NewChannel2(mAIALocation, nullptr, nullptr, - nullptr, // aLoadingNode - nsContentUtils::GetSystemPrincipal(), - nullptr, // aTriggeringPrincipal - nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - nsIContentPolicy::TYPE_OTHER, getter_AddRefs(channel)); + rv = ios->NewChannel(mAIALocation, nullptr, nullptr, + nullptr, // aLoadingNode + nsContentUtils::GetSystemPrincipal(), + nullptr, // aTriggeringPrincipal + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, getter_AddRefs(channel)); if (NS_FAILED(rv)) { return NotifyDone(rv, lock); } diff --git a/security/nss/TAG-INFO b/security/nss/TAG-INFO index 9acc18ba970b..335f16ae2985 100644 --- a/security/nss/TAG-INFO +++ b/security/nss/TAG-INFO @@ -1 +1 @@ -b7713856ebf2 +1f04eea8834a diff --git a/security/nss/automation/abi-check/expected-report-libssl3.so.txt b/security/nss/automation/abi-check/expected-report-libssl3.so.txt index e69de29bb2d1..8ef488de0758 100644 --- a/security/nss/automation/abi-check/expected-report-libssl3.so.txt +++ b/security/nss/automation/abi-check/expected-report-libssl3.so.txt @@ -0,0 +1,20 @@ + +2 functions with some indirect sub-type change: + + [C]'function SECStatus SSL_GetCipherSuiteInfo(PRUint16, SSLCipherSuiteInfo*, PRUintn)' at sslinfo.c:326:1 has some indirect sub-type changes: + parameter 2 of type 'SSLCipherSuiteInfo*' has sub-type changes: + in pointed to type 'typedef SSLCipherSuiteInfo' at sslt.h:433:1: + underlying type 'struct SSLCipherSuiteInfoStr' at sslt.h:366:1 changed: + type size changed from 768 to 832 (in bits) + 1 data member insertion: + 'SSLHashType SSLCipherSuiteInfoStr::kdfHash', at offset 768 (in bits) at sslt.h:429:1 + + [C]'function SECStatus SSL_GetPreliminaryChannelInfo(PRFileDesc*, SSLPreliminaryChannelInfo*, PRUintn)' at sslinfo.c:111:1 has some indirect sub-type changes: + parameter 2 of type 'SSLPreliminaryChannelInfo*' has sub-type changes: + in pointed to type 'typedef SSLPreliminaryChannelInfo' at sslt.h:379:1: + underlying type 'struct SSLPreliminaryChannelInfoStr' at sslt.h:333:1 changed: + type size changed from 160 to 192 (in bits) + 1 data member insertion: + 'PRUint16 SSLPreliminaryChannelInfoStr::zeroRttCipherSuite', at offset 160 (in bits) at sslt.h:375:1 + + diff --git a/security/nss/cmd/manifest.mn b/security/nss/cmd/manifest.mn index be53d3c4eb63..695177c9dccf 100644 --- a/security/nss/cmd/manifest.mn +++ b/security/nss/cmd/manifest.mn @@ -56,6 +56,7 @@ NSS_SRCDIRS = \ p7sign \ p7verify \ pk12util \ + pk11importtest \ pk11ectest \ pk11gcmtest \ pk11mode \ diff --git a/security/nss/cmd/pk11importtest/Makefile b/security/nss/cmd/pk11importtest/Makefile new file mode 100644 index 000000000000..fc8358d5adff --- /dev/null +++ b/security/nss/cmd/pk11importtest/Makefile @@ -0,0 +1,43 @@ +#! gmake +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +####################################################################### +# (1) Include initial platform-independent assignments (MANDATORY). # +####################################################################### + +include manifest.mn + +####################################################################### +# (2) Include "global" configuration information. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/config.mk + +####################################################################### +# (3) Include "component" configuration information. (OPTIONAL) # +####################################################################### + +####################################################################### +# (4) Include "local" platform-dependent assignments (OPTIONAL). # +####################################################################### + +include ../platlibs.mk + +####################################################################### +# (5) Execute "global" rules. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/rules.mk + +####################################################################### +# (6) Execute "component" rules. (OPTIONAL) # +####################################################################### + +####################################################################### +# (7) Execute "local" rules. (OPTIONAL). # +####################################################################### + +include ../platrules.mk diff --git a/security/nss/cmd/pk11importtest/manifest.mn b/security/nss/cmd/pk11importtest/manifest.mn new file mode 100644 index 000000000000..5c40a8108a99 --- /dev/null +++ b/security/nss/cmd/pk11importtest/manifest.mn @@ -0,0 +1,15 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +CORE_DEPTH = ../.. + +MODULE = nss + +CSRCS = pk11importtest.c \ + $(NULL) + +REQUIRES = seccmd + +PROGRAM = pk11importtest + diff --git a/security/nss/cmd/pk11importtest/pk11importtest.c b/security/nss/cmd/pk11importtest/pk11importtest.c new file mode 100644 index 000000000000..b2f489060982 --- /dev/null +++ b/security/nss/cmd/pk11importtest/pk11importtest.c @@ -0,0 +1,406 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "secutil.h" +#include "secmod.h" +#include "cert.h" +#include "secoid.h" +#include "nss.h" +#include "pk11pub.h" +#include "pk11pqg.h" + +/* NSPR 2.0 header files */ +#include "prinit.h" +#include "prprf.h" +#include "prsystem.h" +#include "prmem.h" +/* Portable layer header files */ +#include "plstr.h" + +SECOidData * +getCurveFromString(char *curve_name) +{ + SECOidTag tag = SEC_OID_SECG_EC_SECP256R1; + + if (PORT_Strcasecmp(curve_name, "NISTP256") == 0) { + } else if (PORT_Strcasecmp(curve_name, "NISTP384") == 0) { + tag = SEC_OID_SECG_EC_SECP384R1; + } else if (PORT_Strcasecmp(curve_name, "NISTP521") == 0) { + tag = SEC_OID_SECG_EC_SECP521R1; + } else if (PORT_Strcasecmp(curve_name, "Curve25519") == 0) { + tag = SEC_OID_CURVE25519; + } + return SECOID_FindOIDByTag(tag); +} + +void +dumpItem(const char *label, const SECItem *item) +{ + int i; + printf("%s = [%d bytes] {", label, item->len); + for (i = 0; i < item->len; i++) { + if ((i & 0xf) == 0) + printf("\n "); + else + printf(", "); + printf("%02x", item->data[i]); + } + printf("};\n"); +} + +SECStatus +handleEncryptedPrivateImportTest(char *progName, PK11SlotInfo *slot, + char *testname, CK_MECHANISM_TYPE genMech, void *params, void *pwArgs) +{ + SECStatus rv = SECSuccess; + SECItem privID = { 0 }; + SECItem pubID = { 0 }; + SECItem pubValue = { 0 }; + SECItem pbePwItem = { 0 }; + SECItem nickname = { 0 }; + SECItem token = { 0 }; + SECKEYPublicKey *pubKey = NULL; + SECKEYPrivateKey *privKey = NULL; + PK11GenericObject *objs = NULL; + PK11GenericObject *obj = NULL; + SECKEYEncryptedPrivateKeyInfo *epki = NULL; + PRBool keyFound = 0; + KeyType keyType; + + fprintf(stderr, "Testing %s PrivateKeyImport ***********************\n", + testname); + + /* generate a temp key */ + privKey = PK11_GenerateKeyPair(slot, genMech, params, &pubKey, + PR_FALSE, PR_TRUE, pwArgs); + if (privKey == NULL) { + SECU_PrintError(progName, "PK11_GenerateKeyPair Failed"); + goto cleanup; + } + + /* wrap the temp key */ + pbePwItem.data = (unsigned char *)"pw"; + pbePwItem.len = 2; + epki = PK11_ExportEncryptedPrivKeyInfo(slot, SEC_OID_AES_256_CBC, + &pbePwItem, privKey, 1, NULL); + if (epki == NULL) { + SECU_PrintError(progName, "PK11_ExportEncryptedPrivKeyInfo Failed"); + goto cleanup; + } + + /* Save the public value, which we will need on import */ + keyType = pubKey->keyType; + switch (keyType) { + case rsaKey: + SECITEM_CopyItem(NULL, &pubValue, &pubKey->u.rsa.modulus); + break; + case dhKey: + SECITEM_CopyItem(NULL, &pubValue, &pubKey->u.dh.publicValue); + break; + case dsaKey: + SECITEM_CopyItem(NULL, &pubValue, &pubKey->u.dsa.publicValue); + break; + case ecKey: + SECITEM_CopyItem(NULL, &pubValue, &pubKey->u.ec.publicValue); + break; + default: + fprintf(stderr, "Unknown keytype = %d\n", keyType); + goto cleanup; + } + if (pubValue.data == NULL) { + SECU_PrintError(progName, "Unable to allocate memory"); + goto cleanup; + } + dumpItem("pubValue", &pubValue); + + /* when Asymetric keys represent session keys, those session keys are + * destroyed when we destroy the Asymetric key representations */ + SECKEY_DestroyPublicKey(pubKey); + pubKey = NULL; + SECKEY_DestroyPrivateKey(privKey); + privKey = NULL; + + /* unwrap the temp key as a perm */ + nickname.data = (unsigned char *)"testKey"; + nickname.len = sizeof("testKey"); + rv = PK11_ImportEncryptedPrivateKeyInfoAndReturnKey( + slot, epki, &pbePwItem, &nickname, &pubValue, + PR_TRUE, PR_TRUE, keyType, 0, &privKey, NULL); + if (rv != SECSuccess) { + SECU_PrintError(progName, "PK11_ImportEncryptedPrivateKeyInfo Failed"); + goto cleanup; + } + + /* verify the public key exists */ + rv = PK11_ReadRawAttribute(PK11_TypePrivKey, privKey, CKA_ID, &privID); + if (rv != SECSuccess) { + SECU_PrintError(progName, + "Couldn't read CKA_ID from pub key, checking next key"); + goto cleanup; + } + dumpItem("privKey CKA_ID", &privID); + objs = PK11_FindGenericObjects(slot, CKO_PUBLIC_KEY); + for (obj = objs; obj; obj = PK11_GetNextGenericObject(obj)) { + rv = PK11_ReadRawAttribute(PK11_TypeGeneric, obj, CKA_ID, &pubID); + if (rv != SECSuccess) { + SECU_PrintError(progName, + "Couldn't read CKA_ID from object, checking next key"); + continue; + } + dumpItem("pubKey CKA_ID", &pubID); + if (!SECITEM_ItemsAreEqual(&privID, &pubID)) { + fprintf(stderr, + "CKA_ID does not match priv key, checking next key\n"); + SECITEM_FreeItem(&pubID, PR_FALSE); + continue; + } + SECITEM_FreeItem(&pubID, PR_FALSE); + rv = PK11_ReadRawAttribute(PK11_TypeGeneric, obj, CKA_TOKEN, &token); + if (rv == SECSuccess) { + if (token.len == 1) { + keyFound = token.data[0]; + } + SECITEM_FreeItem(&token, PR_FALSE); + } + if (keyFound) { + printf("matching public key found\n"); + break; + } + printf("Matching key was not a token key, checking next key\n"); + } + +cleanup: + if (objs) { + PK11_DestroyGenericObjects(objs); + } + SECITEM_FreeItem(&pubValue, PR_FALSE); + SECITEM_FreeItem(&privID, PR_FALSE); + PORT_FreeArena(epki->arena, PR_TRUE); + SECKEY_DestroyPublicKey(pubKey); + SECKEY_DestroyPrivateKey(privKey); + fprintf(stderr, "%s PrivateKeyImport %s ***********************\n", + testname, keyFound ? "PASSED" : "FAILED"); + return keyFound ? SECSuccess : SECFailure; +} + +static const char *const usageInfo[] = { + "pk11import - test PK11_PrivateKeyImport()" + "Options:", + " -d certdir directory containing cert database", + " -k keysize size of the rsa, dh, and dsa key to test (default 1024)", + " -C ecc_curve ecc curve (default )", + " -f pwFile file to fetch the password from", + " -p pwString password", + " -r skip rsa test", + " -D skip dsa test", + " -h skip dh test", + " -e skip ec test", +}; +static int nUsageInfo = sizeof(usageInfo) / sizeof(char *); + +static void +Usage(char *progName, FILE *outFile) +{ + int i; + fprintf(outFile, "Usage: %s [ commands ] options\n", progName); + for (i = 0; i < nUsageInfo; i++) + fprintf(outFile, "%s\n", usageInfo[i]); + exit(-1); +} + +enum { + opt_CertDir, + opt_KeySize, + opt_ECCurve, + opt_PWFile, + opt_PWString, + opt_NoRSA, + opt_NoDSA, + opt_NoEC, + opt_NoDH +}; + +static secuCommandFlag options[] = + { + { /* opt_CertDir */ 'd', PR_TRUE, 0, PR_FALSE }, + { /* opt_KeySize */ 'k', PR_TRUE, 0, PR_FALSE }, + { /* opt_ECCurve */ 'C', PR_TRUE, 0, PR_FALSE }, + { /* opt_PWFile */ 'f', PR_TRUE, 0, PR_FALSE }, + { /* opt_PWString */ 'p', PR_TRUE, 0, PR_FALSE }, + { /* opt_NORSA */ 'r', PR_TRUE, 0, PR_FALSE }, + { /* opt_NoDSA */ 'D', PR_TRUE, 0, PR_FALSE }, + { /* opt_NoDH */ 'h', PR_TRUE, 0, PR_FALSE }, + { /* opt_NoEC */ 'e', PR_TRUE, 0, PR_FALSE }, + }; + +int +main(int argc, char **argv) +{ + char *progName; + SECStatus rv; + secuCommand args; + PK11SlotInfo *slot = NULL; + PRBool failed = PR_FALSE; + secuPWData pwArgs = { PW_NONE, 0 }; + PRBool doRSA = PR_TRUE; + PRBool doDSA = PR_TRUE; + PRBool doDH = PR_FALSE; /* NSS currently can't export wrapped DH keys */ + PRBool doEC = PR_TRUE; + PQGParams *pqgParams = NULL; + int keySize; + + args.numCommands = 0; + args.numOptions = sizeof(options) / sizeof(secuCommandFlag); + args.commands = NULL; + args.options = options; + +#ifdef XP_PC + progName = strrchr(argv[0], '\\'); +#else + progName = strrchr(argv[0], '/'); +#endif + progName = progName ? progName + 1 : argv[0]; + + rv = SECU_ParseCommandLine(argc, argv, progName, &args); + if (SECSuccess != rv) { + Usage(progName, stderr); + } + + /* Set the certdb directory (default is ~/.netscape) */ + rv = NSS_InitReadWrite(SECU_ConfigDirectory(args.options[opt_CertDir].arg)); + if (rv != SECSuccess) { + SECU_PrintPRandOSError(progName); + return 255; + } + PK11_SetPasswordFunc(SECU_GetModulePassword); + + /* below here, goto cleanup */ + SECU_RegisterDynamicOids(); + + /* handle the arguments */ + if (args.options[opt_PWFile].arg) { + pwArgs.source = PW_FROMFILE; + pwArgs.data = args.options[opt_PWFile].arg; + } + if (args.options[opt_PWString].arg) { + pwArgs.source = PW_PLAINTEXT; + pwArgs.data = args.options[opt_PWString].arg; + } + if (args.options[opt_NoRSA].activated) { + doRSA = PR_FALSE; + } + if (args.options[opt_NoDSA].activated) { + doDSA = PR_FALSE; + } + if (args.options[opt_NoDH].activated) { + doDH = PR_FALSE; + } + if (args.options[opt_NoEC].activated) { + doEC = PR_FALSE; + } + + slot = PK11_GetInternalKeySlot(); + if (slot == NULL) { + SECU_PrintError(progName, "Couldn't find the internal key slot\n"); + return 255; + } + rv = PK11_Authenticate(slot, PR_TRUE, &pwArgs); + if (rv != SECSuccess) { + SECU_PrintError(progName, "Failed to log into slot"); + PK11_FreeSlot(slot); + return 255; + } + + keySize = 1024; + if (args.options[opt_KeySize].activated && + args.options[opt_KeySize].arg) { + keySize = atoi(args.options[opt_KeySize].arg); + } + + if (doDSA || doDH) { + PQGVerify *pqgVfy; + rv = PK11_PQG_ParamGenV2(keySize, 0, keySize / 16, &pqgParams, &pqgVfy); + if (rv == SECSuccess) { + PK11_PQG_DestroyVerify(pqgVfy); + } else { + SECU_PrintError(progName, + "PK11_PQG_ParamGenV2 failed, can't test DH or DSA"); + doDSA = doDH = PR_FALSE; + failed = PR_TRUE; + } + } + + if (doRSA) { + PK11RSAGenParams rsaParams; + rsaParams.keySizeInBits = keySize; + rsaParams.pe = 0x010001; + rv = handleEncryptedPrivateImportTest(progName, slot, "RSA", + CKM_RSA_PKCS_KEY_PAIR_GEN, &rsaParams, &pwArgs); + if (rv != SECSuccess) { + fprintf(stderr, "RSA Import Failed!\n"); + failed = PR_TRUE; + } + } + if (doDSA) { + rv = handleEncryptedPrivateImportTest(progName, slot, "DSA", + CKM_DSA_KEY_PAIR_GEN, pqgParams, &pwArgs); + if (rv != SECSuccess) { + fprintf(stderr, "DSA Import Failed!\n"); + failed = PR_TRUE; + } + } + if (doDH) { + SECKEYDHParams dhParams; + dhParams.prime = pqgParams->prime; + dhParams.base = pqgParams->base; + rv = handleEncryptedPrivateImportTest(progName, slot, "DH", + CKM_DH_PKCS_KEY_PAIR_GEN, &dhParams, &pwArgs); + if (rv != SECSuccess) { + fprintf(stderr, "DH Import Failed!\n"); + failed = PR_TRUE; + } + } + if (doEC) { + SECKEYECParams ecParams; + SECOidData *curve = SECOID_FindOIDByTag(SEC_OID_SECG_EC_SECP256R1); + if (args.options[opt_ECCurve].activated && + args.options[opt_ECCurve].arg) { + curve = getCurveFromString(args.options[opt_ECCurve].arg); + } + ecParams.data = PORT_Alloc(curve->oid.len + 2); + if (ecParams.data == NULL) { + rv = SECFailure; + goto ec_failed; + } + ecParams.data[0] = SEC_ASN1_OBJECT_ID; + ecParams.data[1] = (unsigned char)curve->oid.len; + PORT_Memcpy(&ecParams.data[2], curve->oid.data, curve->oid.len); + ecParams.len = curve->oid.len + 2; + rv = handleEncryptedPrivateImportTest(progName, slot, "ECC", + CKM_EC_KEY_PAIR_GEN, &ecParams, &pwArgs); + PORT_Free(ecParams.data); + ec_failed: + if (rv != SECSuccess) { + fprintf(stderr, "ECC Import Failed!\n"); + failed = PR_TRUE; + } + } + + if (pqgParams) { + PK11_PQG_DestroyParams(pqgParams); + } + + if (slot) { + PK11_FreeSlot(slot); + } + + rv = NSS_Shutdown(); + if (rv != SECSuccess) { + fprintf(stderr, "Shutdown failed\n"); + SECU_PrintPRandOSError(progName); + return 255; + } + + return failed ? 1 : 0; +} diff --git a/security/nss/cmd/pk11importtest/pk11importtest.gyp b/security/nss/cmd/pk11importtest/pk11importtest.gyp new file mode 100644 index 000000000000..4fce05962175 --- /dev/null +++ b/security/nss/cmd/pk11importtest/pk11importtest.gyp @@ -0,0 +1,25 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +{ + 'includes': [ + '../../coreconf/config.gypi', + '../../cmd/platlibs.gypi' + ], + 'targets': [ + { + 'target_name': 'pk11importtest', + 'type': 'executable', + 'sources': [ + 'pk11importtest.c' + ], + 'dependencies': [ + '<(DEPTH)/exports.gyp:dbm_exports', + '<(DEPTH)/exports.gyp:nss_exports' + ] + } + ], + 'variables': { + 'module': 'nss' + } +} diff --git a/security/nss/coreconf/coreconf.dep b/security/nss/coreconf/coreconf.dep index 590d1bfaeee3..5182f75552c8 100644 --- a/security/nss/coreconf/coreconf.dep +++ b/security/nss/coreconf/coreconf.dep @@ -10,4 +10,3 @@ */ #error "Do not include this header file." - diff --git a/security/nss/cpputil/nss_scoped_ptrs.h b/security/nss/cpputil/nss_scoped_ptrs.h index 03979f2c5834..450e787af53f 100644 --- a/security/nss/cpputil/nss_scoped_ptrs.h +++ b/security/nss/cpputil/nss_scoped_ptrs.h @@ -11,6 +11,7 @@ #include "cert.h" #include "keyhi.h" #include "p12.h" +#include "pk11pqg.h" #include "pk11pub.h" #include "pkcs11uri.h" @@ -41,6 +42,7 @@ struct ScopedDelete { void operator()(PLArenaPool* arena) { PORT_FreeArena(arena, PR_FALSE); } void operator()(PK11Context* context) { PK11_DestroyContext(context, true); } void operator()(PK11GenericObject* obj) { PK11_DestroyGenericObject(obj); } + void operator()(PQGParams* pqg) { PK11_PQG_DestroyParams(pqg); } void operator()(SEC_PKCS12DecoderContext* dcx) { SEC_PKCS12DecoderFinish(dcx); } @@ -66,6 +68,7 @@ SCOPED(CERTName); SCOPED(CERTSubjectPublicKeyInfo); SCOPED(PK11SlotInfo); SCOPED(PK11SymKey); +SCOPED(PQGParams); SCOPED(PRFileDesc); SCOPED(SECAlgorithmID); SCOPED(SECKEYEncryptedPrivateKeyInfo); @@ -82,4 +85,9 @@ SCOPED(CERTDistNames); #undef SCOPED +struct StackSECItem : public SECItem { + StackSECItem() : SECItem({siBuffer, nullptr, 0}) {} + ~StackSECItem() { SECITEM_FreeItem(this, PR_FALSE); } +}; + #endif // nss_scoped_ptrs_h__ diff --git a/security/nss/cpputil/scoped_ptrs_util.h b/security/nss/cpputil/scoped_ptrs_util.h index 2dbf34e1d36c..dc6e1d7525d1 100644 --- a/security/nss/cpputil/scoped_ptrs_util.h +++ b/security/nss/cpputil/scoped_ptrs_util.h @@ -33,6 +33,7 @@ struct ScopedMaybeDelete { SCOPED(SECAlgorithmID); SCOPED(SECItem); SCOPED(PK11URI); +SCOPED(PLArenaPool); #undef SCOPED diff --git a/security/nss/cpputil/tls_parser.h b/security/nss/cpputil/tls_parser.h index cd9e28fc35bc..881c5268ec58 100644 --- a/security/nss/cpputil/tls_parser.h +++ b/security/nss/cpputil/tls_parser.h @@ -80,6 +80,32 @@ inline std::ostream& operator<<(std::ostream& os, SSLProtocolVariant v) { return os << ((v == ssl_variant_stream) ? "TLS" : "DTLS"); } +inline std::ostream& operator<<(std::ostream& os, SSLContentType v) { + switch (v) { + case ssl_ct_change_cipher_spec: + return os << "CCS"; + case ssl_ct_alert: + return os << "alert"; + case ssl_ct_handshake: + return os << "handshake"; + case ssl_ct_application_data: + return os << "application data"; + case ssl_ct_ack: + return os << "ack"; + } + return os << "UNKNOWN content type " << static_cast(v); +} + +inline std::ostream& operator<<(std::ostream& os, SSLSecretDirection v) { + switch (v) { + case ssl_secret_read: + return os << "read"; + case ssl_secret_write: + return os << "write"; + } + return os << "UNKNOWN secret direction " << static_cast(v); +} + inline bool IsDtls(uint16_t version) { return (version & 0x8000) == 0x8000; } inline uint16_t NormalizeTlsVersion(uint16_t version) { diff --git a/security/nss/gtests/common/gtests.cc b/security/nss/gtests/common/gtests.cc index bd5a97a8ed61..7e585791b8a7 100644 --- a/security/nss/gtests/common/gtests.cc +++ b/security/nss/gtests/common/gtests.cc @@ -10,7 +10,23 @@ int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); - if (NSS_NoDB_Init(nullptr) != SECSuccess) { + const char *workdir = ""; + uint32_t flags = NSS_INIT_READONLY; + + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "-d")) { + if (i + 1 >= argc) { + PR_fprintf(PR_STDERR, "Usage: %s [-d [-w]]\n", argv[0]); + exit(2); + } + workdir = argv[i + 1]; + i++; + } else if (!strcmp(argv[i], "-w")) { + flags &= ~NSS_INIT_READONLY; + } + } + + if (NSS_Initialize(workdir, "", "", SECMOD_DB, flags) != SECSuccess) { return 1; } if (NSS_SetDomesticPolicy() != SECSuccess) { diff --git a/security/nss/gtests/pk11_gtest/manifest.mn b/security/nss/gtests/pk11_gtest/manifest.mn index ea7b43a2b043..75ddb71c3144 100644 --- a/security/nss/gtests/pk11_gtest/manifest.mn +++ b/security/nss/gtests/pk11_gtest/manifest.mn @@ -13,6 +13,7 @@ CPPSRCS = \ pk11_ecdsa_unittest.cc \ pk11_encrypt_derive_unittest.cc \ pk11_export_unittest.cc \ + pk11_import_unittest.cc \ pk11_pbkdf2_unittest.cc \ pk11_prf_unittest.cc \ pk11_prng_unittest.cc \ @@ -33,4 +34,3 @@ EXTRA_LIBS = $(DIST)/lib/$(LIB_PREFIX)gtest.$(LIB_SUFFIX) \ $(DIST)/lib/$(LIB_PREFIX)cpputil.$(LIB_SUFFIX) \ $(DIST)/lib/$(LIB_PREFIX)gtestutil.$(LIB_SUFFIX) \ $(NULL) - diff --git a/security/nss/gtests/pk11_gtest/pk11_gtest.gyp b/security/nss/gtests/pk11_gtest/pk11_gtest.gyp index c73139b05a1e..c8d04f017a0c 100644 --- a/security/nss/gtests/pk11_gtest/pk11_gtest.gyp +++ b/security/nss/gtests/pk11_gtest/pk11_gtest.gyp @@ -18,6 +18,7 @@ 'pk11_curve25519_unittest.cc', 'pk11_ecdsa_unittest.cc', 'pk11_encrypt_derive_unittest.cc', + 'pk11_import_unittest.cc', 'pk11_pbkdf2_unittest.cc', 'pk11_prf_unittest.cc', 'pk11_prng_unittest.cc', diff --git a/security/nss/gtests/ssl_gtest/libssl_internals.c b/security/nss/gtests/ssl_gtest/libssl_internals.c index e43113de4209..2a9803daabbd 100644 --- a/security/nss/gtests/ssl_gtest/libssl_internals.c +++ b/security/nss/gtests/ssl_gtest/libssl_internals.c @@ -297,38 +297,6 @@ SSLKEAType SSLInt_GetKEAType(SSLNamedGroup group) { return groupDef->keaType; } -SECStatus SSLInt_SetCipherSpecChangeFunc(PRFileDesc *fd, - sslCipherSpecChangedFunc func, - void *arg) { - sslSocket *ss; - - ss = ssl_FindSocket(fd); - if (!ss) { - return SECFailure; - } - - ss->ssl3.changedCipherSpecFunc = func; - ss->ssl3.changedCipherSpecArg = arg; - - return SECSuccess; -} - -PK11SymKey *SSLInt_CipherSpecToKey(const ssl3CipherSpec *spec) { - return spec->keyMaterial.key; -} - -SSLCipherAlgorithm SSLInt_CipherSpecToAlgorithm(const ssl3CipherSpec *spec) { - return spec->cipherDef->calg; -} - -const PRUint8 *SSLInt_CipherSpecToIv(const ssl3CipherSpec *spec) { - return spec->keyMaterial.iv; -} - -PRUint16 SSLInt_CipherSpecToEpoch(const ssl3CipherSpec *spec) { - return spec->epoch; -} - void SSLInt_SetTicketLifetime(uint32_t lifetime) { ssl_ticket_lifetime = lifetime; } @@ -360,16 +328,14 @@ void SSLInt_RolloverAntiReplay(void) { tls13_AntiReplayRollover(ssl_TimeUsec()); } -SECStatus SSLInt_GetEpochs(PRFileDesc *fd, PRUint16 *readEpoch, - PRUint16 *writeEpoch) { +SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending) { sslSocket *ss = ssl_FindSocket(fd); - if (!ss || !readEpoch || !writeEpoch) { + if (!ss) { return SECFailure; } - ssl_GetSpecReadLock(ss); - *readEpoch = ss->ssl3.crSpec->epoch; - *writeEpoch = ss->ssl3.cwSpec->epoch; - ssl_ReleaseSpecReadLock(ss); + ssl_GetSSL3HandshakeLock(ss); + *pending = ss->ssl3.hs.msg_body.len > 0; + ssl_ReleaseSSL3HandshakeLock(ss); return SECSuccess; } diff --git a/security/nss/gtests/ssl_gtest/libssl_internals.h b/security/nss/gtests/ssl_gtest/libssl_internals.h index 3efb362c2742..8c78de28f51a 100644 --- a/security/nss/gtests/ssl_gtest/libssl_internals.h +++ b/security/nss/gtests/ssl_gtest/libssl_internals.h @@ -39,16 +39,7 @@ SECStatus SSLInt_AdvanceWriteSeqNum(PRFileDesc *fd, PRUint64 to); SECStatus SSLInt_AdvanceReadSeqNum(PRFileDesc *fd, PRUint64 to); SECStatus SSLInt_AdvanceWriteSeqByAWindow(PRFileDesc *fd, PRInt32 extra); SSLKEAType SSLInt_GetKEAType(SSLNamedGroup group); -SECStatus SSLInt_GetEpochs(PRFileDesc *fd, PRUint16 *readEpoch, - PRUint16 *writeEpoch); - -SECStatus SSLInt_SetCipherSpecChangeFunc(PRFileDesc *fd, - sslCipherSpecChangedFunc func, - void *arg); -PRUint16 SSLInt_CipherSpecToEpoch(const ssl3CipherSpec *spec); -PK11SymKey *SSLInt_CipherSpecToKey(const ssl3CipherSpec *spec); -SSLCipherAlgorithm SSLInt_CipherSpecToAlgorithm(const ssl3CipherSpec *spec); -const PRUint8 *SSLInt_CipherSpecToIv(const ssl3CipherSpec *spec); +SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending); void SSLInt_SetTicketLifetime(uint32_t lifetime); SECStatus SSLInt_SetSocketMaxEarlyDataSize(PRFileDesc *fd, uint32_t size); void SSLInt_RolloverAntiReplay(void); diff --git a/security/nss/gtests/ssl_gtest/manifest.mn b/security/nss/gtests/ssl_gtest/manifest.mn index 7f4ee7953906..9df798b64f6c 100644 --- a/security/nss/gtests/ssl_gtest/manifest.mn +++ b/security/nss/gtests/ssl_gtest/manifest.mn @@ -36,6 +36,7 @@ CPPSRCS = \ ssl_loopback_unittest.cc \ ssl_misc_unittest.cc \ ssl_record_unittest.cc \ + ssl_recordsep_unittest.cc \ ssl_recordsize_unittest.cc \ ssl_resumption_unittest.cc \ ssl_renegotiation_unittest.cc \ diff --git a/security/nss/gtests/ssl_gtest/ssl_auth_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_auth_unittest.cc index 3a52ac20c5cb..8db23e74e159 100644 --- a/security/nss/gtests/ssl_gtest/ssl_auth_unittest.cc +++ b/security/nss/gtests/ssl_gtest/ssl_auth_unittest.cc @@ -176,6 +176,321 @@ TEST_P(TlsConnectGeneric, ClientAuth) { CheckKeys(); } +class TlsCertificateRequestContextRecorder : public TlsHandshakeFilter { + public: + TlsCertificateRequestContextRecorder(const std::shared_ptr& a, + uint8_t handshake_type) + : TlsHandshakeFilter(a, {handshake_type}), buffer_(), filtered_(false) { + EnableDecryption(); + } + + bool filtered() const { return filtered_; } + const DataBuffer& buffer() const { return buffer_; } + + protected: + virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header, + const DataBuffer& input, + DataBuffer* output) { + assert(1 < input.len()); + size_t len = input.data()[0]; + assert(len + 1 < input.len()); + buffer_.Assign(input.data() + 1, len); + filtered_ = true; + return KEEP; + } + + private: + DataBuffer buffer_; + bool filtered_; +}; + +// All stream only tests; DTLS isn't supported yet. + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuth) { + EnsureTlsSetup(); + auto capture_cert_req = MakeTlsFilter( + server_, kTlsHandshakeCertificateRequest); + auto capture_certificate = + MakeTlsFilter( + client_, kTlsHandshakeCertificate); + client_->SetupClientAuth(); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + size_t called = 0; + server_->SetAuthCertificateCallback( + [&called](TlsAgent*, PRBool, PRBool) -> SECStatus { + called++; + return SECSuccess; + }); + Connect(); + EXPECT_EQ(0U, called); + EXPECT_FALSE(capture_cert_req->filtered()); + EXPECT_FALSE(capture_certificate->filtered()); + // Send CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + // Need to do a round-trip so that the post-handshake message is + // handled on both client and server. + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ReadBytes(50); + EXPECT_EQ(1U, called); + EXPECT_TRUE(capture_cert_req->filtered()); + EXPECT_TRUE(capture_certificate->filtered()); + // Check if a non-empty request context is generated and it is + // properly sent back. + EXPECT_LT(0U, capture_cert_req->buffer().len()); + EXPECT_EQ(capture_cert_req->buffer().len(), + capture_certificate->buffer().len()); + EXPECT_EQ(0, memcmp(capture_cert_req->buffer().data(), + capture_certificate->buffer().data(), + capture_cert_req->buffer().len())); + ScopedCERTCertificate cert1(SSL_PeerCertificate(server_->ssl_fd())); + ScopedCERTCertificate cert2(SSL_LocalCertificate(client_->ssl_fd())); + EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert1->derCert, &cert2->derCert)); +} + +static SECStatus GetClientAuthDataHook(void* self, PRFileDesc* fd, + CERTDistNames* caNames, + CERTCertificate** clientCert, + SECKEYPrivateKey** clientKey) { + ScopedCERTCertificate cert; + ScopedSECKEYPrivateKey priv; + // use a different certificate than TlsAgent::kClient + if (!TlsAgent::LoadCertificate(TlsAgent::kRsa2048, &cert, &priv)) { + return SECFailure; + } + + *clientCert = cert.release(); + *clientKey = priv.release(); + return SECSuccess; +} + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthMultiple) { + client_->SetupClientAuth(); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + size_t called = 0; + server_->SetAuthCertificateCallback( + [&called](TlsAgent*, PRBool, PRBool) -> SECStatus { + called++; + return SECSuccess; + }); + Connect(); + EXPECT_EQ(0U, called); + EXPECT_EQ(nullptr, SSL_PeerCertificate(server_->ssl_fd())); + // Send 1st CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ReadBytes(50); + EXPECT_EQ(1U, called); + ScopedCERTCertificate cert1(SSL_PeerCertificate(server_->ssl_fd())); + ScopedCERTCertificate cert2(SSL_LocalCertificate(client_->ssl_fd())); + EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert1->derCert, &cert2->derCert)); + // Send 2nd CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_GetClientAuthDataHook( + client_->ssl_fd(), GetClientAuthDataHook, nullptr)); + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ReadBytes(50); + EXPECT_EQ(2U, called); + ScopedCERTCertificate cert3(SSL_PeerCertificate(server_->ssl_fd())); + ScopedCERTCertificate cert4(SSL_LocalCertificate(client_->ssl_fd())); + EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert3->derCert, &cert4->derCert)); + EXPECT_FALSE(SECITEM_ItemsAreEqual(&cert3->derCert, &cert1->derCert)); +} + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthConcurrent) { + client_->SetupClientAuth(); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + Connect(); + // Send 1st CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + // Send 2nd CertificateRequest. + EXPECT_EQ(SECFailure, SSL_SendCertificateRequest(server_->ssl_fd())); + EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError()); +} + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthMissingExtension) { + client_->SetupClientAuth(); + Connect(); + // Send CertificateRequest, should fail due to missing + // post_handshake_auth extension. + EXPECT_EQ(SECFailure, SSL_SendCertificateRequest(server_->ssl_fd())); + EXPECT_EQ(SSL_ERROR_MISSING_POST_HANDSHAKE_AUTH_EXTENSION, PORT_GetError()); +} + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthAfterClientAuth) { + client_->SetupClientAuth(); + server_->RequestClientAuth(true); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + size_t called = 0; + server_->SetAuthCertificateCallback( + [&called](TlsAgent*, PRBool, PRBool) -> SECStatus { + called++; + return SECSuccess; + }); + Connect(); + EXPECT_EQ(1U, called); + ScopedCERTCertificate cert1(SSL_PeerCertificate(server_->ssl_fd())); + ScopedCERTCertificate cert2(SSL_LocalCertificate(client_->ssl_fd())); + EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert1->derCert, &cert2->derCert)); + // Send CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_GetClientAuthDataHook( + client_->ssl_fd(), GetClientAuthDataHook, nullptr)); + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ReadBytes(50); + EXPECT_EQ(2U, called); + ScopedCERTCertificate cert3(SSL_PeerCertificate(server_->ssl_fd())); + ScopedCERTCertificate cert4(SSL_LocalCertificate(client_->ssl_fd())); + EXPECT_TRUE(SECITEM_ItemsAreEqual(&cert3->derCert, &cert4->derCert)); + EXPECT_FALSE(SECITEM_ItemsAreEqual(&cert3->derCert, &cert1->derCert)); +} + +// Damages the request context in a CertificateRequest message. +// We don't modify a Certificate message instead, so that the client +// can compute CertificateVerify correctly. +class TlsDamageCertificateRequestContextFilter : public TlsHandshakeFilter { + public: + TlsDamageCertificateRequestContextFilter(const std::shared_ptr& a) + : TlsHandshakeFilter(a, {kTlsHandshakeCertificateRequest}) { + EnableDecryption(); + } + + protected: + virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header, + const DataBuffer& input, + DataBuffer* output) { + *output = input; + assert(1 < output->len()); + // The request context has a 1 octet length. + output->data()[1] ^= 73; + return CHANGE; + } +}; + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthContextMismatch) { + EnsureTlsSetup(); + MakeTlsFilter(server_); + client_->SetupClientAuth(); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + Connect(); + // Send CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ExpectSendAlert(kTlsAlertIllegalParameter); + server_->ReadBytes(50); + EXPECT_EQ(SSL_ERROR_RX_MALFORMED_CERTIFICATE, PORT_GetError()); + server_->ExpectReadWriteError(); + server_->SendData(50); + client_->ExpectReceiveAlert(kTlsAlertIllegalParameter); + client_->ReadBytes(50); + EXPECT_EQ(SSL_ERROR_ILLEGAL_PARAMETER_ALERT, PORT_GetError()); +} + +// Replaces signature in a CertificateVerify message. +class TlsDamageSignatureFilter : public TlsHandshakeFilter { + public: + TlsDamageSignatureFilter(const std::shared_ptr& a) + : TlsHandshakeFilter(a, {kTlsHandshakeCertificateVerify}) { + EnableDecryption(); + } + + protected: + virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header, + const DataBuffer& input, + DataBuffer* output) { + *output = input; + assert(2 < output->len()); + // The signature follows a 2-octet signature scheme. + output->data()[2] ^= 73; + return CHANGE; + } +}; + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthBadSignature) { + EnsureTlsSetup(); + MakeTlsFilter(client_); + client_->SetupClientAuth(); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + Connect(); + // Send CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ExpectSendAlert(kTlsAlertDecodeError); + server_->ReadBytes(50); + EXPECT_EQ(SSL_ERROR_RX_MALFORMED_CERT_VERIFY, PORT_GetError()); +} + +TEST_F(TlsConnectStreamTls13, PostHandshakeAuthDecline) { + EnsureTlsSetup(); + auto capture_cert_req = MakeTlsFilter( + server_, kTlsHandshakeCertificateRequest); + auto capture_certificate = + MakeTlsFilter( + client_, kTlsHandshakeCertificate); + client_->SetupClientAuth(); + EXPECT_EQ(SECSuccess, SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_POST_HANDSHAKE_AUTH, PR_TRUE)); + // Client to decline the certificate request. + EXPECT_EQ(SECSuccess, + SSL_GetClientAuthDataHook( + client_->ssl_fd(), + [](void*, PRFileDesc*, CERTDistNames*, CERTCertificate**, + SECKEYPrivateKey**) -> SECStatus { return SECFailure; }, + nullptr)); + size_t called = 0; + server_->SetAuthCertificateCallback( + [&called](TlsAgent*, PRBool, PRBool) -> SECStatus { + called++; + return SECSuccess; + }); + Connect(); + EXPECT_EQ(0U, called); + // Send CertificateRequest. + EXPECT_EQ(SECSuccess, SSL_SendCertificateRequest(server_->ssl_fd())) + << "Unexpected error: " << PORT_ErrorToName(PORT_GetError()); + server_->SendData(50); + client_->ReadBytes(50); + client_->SendData(50); + server_->ReadBytes(50); + // AuthCertificateCallback is not called, because the client sends + // an empty certificate_list. + EXPECT_EQ(0U, called); + EXPECT_TRUE(capture_cert_req->filtered()); + EXPECT_TRUE(capture_certificate->filtered()); + // Check if a non-empty request context is generated and it is + // properly sent back. + EXPECT_LT(0U, capture_cert_req->buffer().len()); + EXPECT_EQ(capture_cert_req->buffer().len(), + capture_certificate->buffer().len()); + EXPECT_EQ(0, memcmp(capture_cert_req->buffer().data(), + capture_certificate->buffer().data(), + capture_cert_req->buffer().len())); +} + // In TLS 1.3, the client sends its cert rejection on the // second flight, and since it has already received the // server's Finished, it transitions to complete and @@ -273,9 +588,7 @@ class TlsReplaceSignatureSchemeFilter : public TlsHandshakeFilter { TlsReplaceSignatureSchemeFilter(const std::shared_ptr& a, SSLSignatureScheme scheme) : TlsHandshakeFilter(a, {kTlsHandshakeCertificateVerify}), - scheme_(scheme) { - EnableDecryption(); - } + scheme_(scheme) {} protected: virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header, @@ -552,7 +865,9 @@ TEST_P(TlsConnectTls12, SignatureAlgorithmDrop) { TEST_P(TlsConnectTls13, UnsupportedSignatureSchemeAlert) { EnsureTlsSetup(); - MakeTlsFilter(server_, ssl_sig_none); + auto filter = + MakeTlsFilter(server_, ssl_sig_none); + filter->EnableDecryption(); ConnectExpectAlert(client_, kTlsAlertIllegalParameter); server_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); @@ -563,8 +878,9 @@ TEST_P(TlsConnectTls13, InconsistentSignatureSchemeAlert) { EnsureTlsSetup(); // This won't work because we use an RSA cert by default. - MakeTlsFilter( + auto filter = MakeTlsFilter( server_, ssl_sig_ecdsa_secp256r1_sha256); + filter->EnableDecryption(); ConnectExpectAlert(client_, kTlsAlertIllegalParameter); server_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); diff --git a/security/nss/gtests/ssl_gtest/ssl_damage_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_damage_unittest.cc index 0723c9bee564..9cbe9566f115 100644 --- a/security/nss/gtests/ssl_gtest/ssl_damage_unittest.cc +++ b/security/nss/gtests/ssl_gtest/ssl_damage_unittest.cc @@ -62,7 +62,6 @@ TEST_P(TlsConnectGenericPre13, DamageServerSignature) { EnsureTlsSetup(); auto filter = MakeTlsFilter( server_, kTlsHandshakeServerKeyExchange); - filter->EnableDecryption(); ExpectAlert(client_, kTlsAlertDecryptError); ConnectExpectFail(); client_->CheckErrorCode(SEC_ERROR_BAD_SIGNATURE); @@ -84,7 +83,9 @@ TEST_P(TlsConnectGeneric, DamageClientSignature) { server_->RequestClientAuth(true); auto filter = MakeTlsFilter( client_, kTlsHandshakeCertificateVerify); - filter->EnableDecryption(); + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + filter->EnableDecryption(); + } server_->ExpectSendAlert(kTlsAlertDecryptError); // Do these handshakes by hand to avoid race condition on // the client processing the server's alert. diff --git a/security/nss/gtests/ssl_gtest/ssl_drop_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_drop_unittest.cc index f25efc77adfe..b441b5c10dba 100644 --- a/security/nss/gtests/ssl_gtest/ssl_drop_unittest.cc +++ b/security/nss/gtests/ssl_gtest/ssl_drop_unittest.cc @@ -66,6 +66,38 @@ TEST_P(TlsConnectDatagramPre13, DropServerSecondFlightThrice) { Connect(); } +static void CheckAcks(const std::shared_ptr& acks, + size_t index, std::vector expected) { + ASSERT_LT(index, acks->count()); + const DataBuffer& buf = acks->record(index).buffer; + size_t offset = 2; + uint64_t len; + + EXPECT_EQ(2 + expected.size() * 8, buf.len()); + ASSERT_TRUE(buf.Read(0, 2, &len)); + ASSERT_EQ(static_cast(len + 2), buf.len()); + if ((2 + expected.size() * 8) != buf.len()) { + while (offset < buf.len()) { + uint64_t ack; + ASSERT_TRUE(buf.Read(offset, 8, &ack)); + offset += 8; + std::cerr << "Ack=0x" << std::hex << ack << std::dec << std::endl; + } + return; + } + + for (size_t i = 0; i < expected.size(); ++i) { + uint64_t a = expected[i]; + uint64_t ack; + ASSERT_TRUE(buf.Read(offset, 8, &ack)); + offset += 8; + if (a != ack) { + ADD_FAILURE() << "Wrong ack " << i << " expected=0x" << std::hex << a + << " got=0x" << ack << std::dec; + } + } +} + class TlsDropDatagram13 : public TlsConnectDatagram13, public ::testing::WithParamInterface { public: @@ -139,37 +171,6 @@ class TlsDropDatagram13 : public TlsConnectDatagram13, std::shared_ptr chain_; }; - void CheckAcks(const DropAckChain& chain, size_t index, - std::vector acks) { - const DataBuffer& buf = chain.ack_->record(index).buffer; - size_t offset = 2; - uint64_t len; - - EXPECT_EQ(2 + acks.size() * 8, buf.len()); - ASSERT_TRUE(buf.Read(0, 2, &len)); - ASSERT_EQ(static_cast(len + 2), buf.len()); - if ((2 + acks.size() * 8) != buf.len()) { - while (offset < buf.len()) { - uint64_t ack; - ASSERT_TRUE(buf.Read(offset, 8, &ack)); - offset += 8; - std::cerr << "Ack=0x" << std::hex << ack << std::dec << std::endl; - } - return; - } - - for (size_t i = 0; i < acks.size(); ++i) { - uint64_t a = acks[i]; - uint64_t ack; - ASSERT_TRUE(buf.Read(offset, 8, &ack)); - offset += 8; - if (a != ack) { - ADD_FAILURE() << "Wrong ack " << i << " expected=0x" << std::hex << a - << " got=0x" << ack << std::dec; - } - } - } - void CheckedHandshakeSendReceive() { Handshake(); CheckPostHandshake(); @@ -199,7 +200,7 @@ TEST_P(TlsDropDatagram13, DropClientFirstFlightOnce) { client_->Handshake(); server_->Handshake(); CheckedHandshakeSendReceive(); - CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); + CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL}); } TEST_P(TlsDropDatagram13, DropServerFirstFlightOnce) { @@ -210,7 +211,7 @@ TEST_P(TlsDropDatagram13, DropServerFirstFlightOnce) { server_->Handshake(); server_filters_.drop_->Disable(); CheckedHandshakeSendReceive(); - CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); + CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL}); } // Dropping the server's first record also does not produce @@ -223,7 +224,7 @@ TEST_P(TlsDropDatagram13, DropServerFirstRecordOnce) { server_->Handshake(); Handshake(); CheckedHandshakeSendReceive(); - CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); + CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL}); } // Dropping the second packet of the server's flight should @@ -236,8 +237,8 @@ TEST_P(TlsDropDatagram13, DropServerSecondRecordOnce) { HandshakeAndAck(client_); expected_client_acks_ = 1; CheckedHandshakeSendReceive(); - CheckAcks(client_filters_, 0, {0}); // ServerHello - CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); + CheckAcks(client_filters_.ack_, 0, {0}); // ServerHello + CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL}); } // Drop the server ACK and verify that the client retransmits @@ -265,8 +266,8 @@ TEST_P(TlsDropDatagram13, DropServerAckOnce) { EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError()); CheckPostHandshake(); // There should be two copies of the finished ACK - CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); - CheckAcks(server_filters_, 1, {0x0002000000000000ULL}); + CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL}); + CheckAcks(server_filters_.ack_, 1, {0x0002000000000000ULL}); } // Drop the client certificate verify. @@ -281,10 +282,10 @@ TEST_P(TlsDropDatagram13, DropClientCertVerify) { expected_server_acks_ = 2; CheckedHandshakeSendReceive(); // Ack of the Cert. - CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); + CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL}); // Ack of the whole client handshake. CheckAcks( - server_filters_, 1, + server_filters_.ack_, 1, {0x0002000000000000ULL, // CH (we drop everything after this on client) 0x0002000000000003ULL, // CT (2) 0x0002000000000004ULL}); // FIN (2) @@ -310,11 +311,11 @@ TEST_P(TlsDropDatagram13, DropFirstHalfOfServerCertificate) { // as the previous CT1). EXPECT_EQ(ct1_size, server_filters_.record(0).buffer.len()); CheckedHandshakeSendReceive(); - CheckAcks(client_filters_, 0, + CheckAcks(client_filters_.ack_, 0, {0, // SH 0x0002000000000000ULL, // EE 0x0002000000000002ULL}); // CT2 - CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); + CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL}); } // Shrink the MTU down so that certs get split and drop the second piece. @@ -336,13 +337,13 @@ TEST_P(TlsDropDatagram13, DropSecondHalfOfServerCertificate) { // Check that the first record is CT1 EXPECT_EQ(ct1_size, server_filters_.record(0).buffer.len()); CheckedHandshakeSendReceive(); - CheckAcks(client_filters_, 0, + CheckAcks(client_filters_.ack_, 0, { 0, // SH 0x0002000000000000ULL, // EE 0x0002000000000001ULL, // CT1 }); - CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); + CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL}); } // In this test, the Certificate message is sent four times, we drop all or part @@ -392,18 +393,18 @@ class TlsFragmentationAndRecoveryTest : public TlsDropDatagram13 { 0, // SH 0x0002000000000000ULL // EE }; - CheckAcks(client_filters_, 0, client_acks); + CheckAcks(client_filters_.ack_, 0, client_acks); // And from the second attempt for the half was kept (we delayed this ACK). client_acks.push_back(0x0002000000000000ULL + second_flight_count + ~dropped_half % 2); - CheckAcks(client_filters_, 1, client_acks); + CheckAcks(client_filters_.ack_, 1, client_acks); // And the third attempt where the first and last thirds got through. client_acks.push_back(0x0002000000000000ULL + second_flight_count + third_flight_count - 1); client_acks.push_back(0x0002000000000000ULL + second_flight_count + third_flight_count + 1); - CheckAcks(client_filters_, 2, client_acks); - CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); + CheckAcks(client_filters_.ack_, 2, client_acks); + CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL}); } private: @@ -548,7 +549,7 @@ TEST_P(TlsDropDatagram13, NoDropsDuringZeroRtt) { CheckConnected(); SendReceive(); EXPECT_EQ(0U, client_filters_.ack_->count()); - CheckAcks(server_filters_, 0, + CheckAcks(server_filters_.ack_, 0, {0x0001000000000001ULL, // EOED 0x0002000000000000ULL}); // Finished } @@ -567,8 +568,8 @@ TEST_P(TlsDropDatagram13, DropEEDuringZeroRtt) { ExpectEarlyDataAccepted(true); CheckConnected(); SendReceive(); - CheckAcks(client_filters_, 0, {0}); - CheckAcks(server_filters_, 0, + CheckAcks(client_filters_.ack_, 0, {0}); + CheckAcks(server_filters_.ack_, 0, {0x0001000000000002ULL, // EOED 0x0002000000000000ULL}); // Finished } @@ -608,22 +609,22 @@ TEST_P(TlsDropDatagram13, ReorderServerEE) { expected_client_acks_ = 1; HandshakeAndAck(client_); CheckedHandshakeSendReceive(); - CheckAcks(client_filters_, 0, + CheckAcks(client_filters_.ack_, 0, { 0, // SH 0x0002000000000000, // EE }); - CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); + CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL}); } // The client sends an out of order non-handshake message // but with the handshake key. class TlsSendCipherSpecCapturer { public: - TlsSendCipherSpecCapturer(std::shared_ptr& agent) - : send_cipher_specs_() { - SSLInt_SetCipherSpecChangeFunc(agent->ssl_fd(), CipherSpecChanged, - (void*)this); + TlsSendCipherSpecCapturer(const std::shared_ptr& agent) + : agent_(agent), send_cipher_specs_() { + EXPECT_EQ(SECSuccess, + SSL_SecretCallback(agent_->ssl_fd(), SecretCallback, this)); } std::shared_ptr spec(size_t i) { @@ -634,28 +635,42 @@ class TlsSendCipherSpecCapturer { } private: - static void CipherSpecChanged(void* arg, PRBool sending, - ssl3CipherSpec* newSpec) { - if (!sending) { + static void SecretCallback(PRFileDesc* fd, PRUint16 epoch, + SSLSecretDirection dir, PK11SymKey* secret, + void* arg) { + auto self = static_cast(arg); + std::cerr << self->agent_->role_str() << ": capture " << dir + << " secret for epoch " << epoch << std::endl; + + if (dir == ssl_secret_read) { return; } - auto self = static_cast(arg); + SSLPreliminaryChannelInfo preinfo; + EXPECT_EQ(SECSuccess, + SSL_GetPreliminaryChannelInfo(self->agent_->ssl_fd(), &preinfo, + sizeof(preinfo))); + EXPECT_EQ(sizeof(preinfo), preinfo.length); + EXPECT_TRUE(preinfo.valuesSet & ssl_preinfo_cipher_suite); - auto spec = std::make_shared(); - bool ret = spec->Init(SSLInt_CipherSpecToEpoch(newSpec), - SSLInt_CipherSpecToAlgorithm(newSpec), - SSLInt_CipherSpecToKey(newSpec), - SSLInt_CipherSpecToIv(newSpec)); - EXPECT_EQ(true, ret); + SSLCipherSuiteInfo cipherinfo; + EXPECT_EQ(SECSuccess, + SSL_GetCipherSuiteInfo(preinfo.cipherSuite, &cipherinfo, + sizeof(cipherinfo))); + EXPECT_EQ(sizeof(cipherinfo), cipherinfo.length); + + auto spec = std::make_shared(true, epoch); + EXPECT_TRUE(spec->SetKeys(&cipherinfo, secret)); self->send_cipher_specs_.push_back(spec); } + std::shared_ptr agent_; std::vector> send_cipher_specs_; }; -TEST_P(TlsDropDatagram13, SendOutOfOrderAppWithHandshakeKey) { +TEST_F(TlsConnectDatagram13, SendOutOfOrderAppWithHandshakeKey) { StartConnect(); + // Capturing secrets means that we can't use decrypting filters on the client. TlsSendCipherSpecCapturer capturer(client_); client_->Handshake(); server_->Handshake(); @@ -680,9 +695,12 @@ TEST_P(TlsDropDatagram13, SendOutOfOrderAppWithHandshakeKey) { EXPECT_EQ(SSL_ERROR_RX_UNKNOWN_RECORD_TYPE, PORT_GetError()); } -TEST_P(TlsDropDatagram13, SendOutOfOrderHsNonsenseWithHandshakeKey) { +TEST_F(TlsConnectDatagram13, SendOutOfOrderHsNonsenseWithHandshakeKey) { StartConnect(); TlsSendCipherSpecCapturer capturer(client_); + auto acks = MakeTlsFilter(server_, ssl_ct_ack); + acks->EnableDecryption(); + client_->Handshake(); server_->Handshake(); client_->Handshake(); @@ -699,10 +717,10 @@ TEST_P(TlsDropDatagram13, SendOutOfOrderHsNonsenseWithHandshakeKey) { ssl_ct_handshake, DataBuffer(buf, sizeof(buf)))); server_->Handshake(); - EXPECT_EQ(2UL, server_filters_.ack_->count()); + EXPECT_EQ(2UL, acks->count()); // The server acknowledges client Finished twice. - CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); - CheckAcks(server_filters_, 1, {0x0002000000000000ULL}); + CheckAcks(acks, 0, {0x0002000000000000ULL}); + CheckAcks(acks, 1, {0x0002000000000000ULL}); } // Shrink the MTU down so that certs get split and then swap the first and @@ -726,7 +744,7 @@ TEST_P(TlsReorderDatagram13, ReorderServerCertificate) { ShiftDtlsTimers(); CheckedHandshakeSendReceive(); EXPECT_EQ(2UL, server_filters_.records_->count()); // ACK + Data - CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); + CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL}); } TEST_P(TlsReorderDatagram13, DataAfterEOEDDuringZeroRtt) { @@ -761,7 +779,8 @@ TEST_P(TlsReorderDatagram13, DataAfterEOEDDuringZeroRtt) { CheckConnected(); EXPECT_EQ(0U, client_filters_.ack_->count()); // Acknowledgements for EOED and Finished. - CheckAcks(server_filters_, 0, {0x0001000000000002ULL, 0x0002000000000000ULL}); + CheckAcks(server_filters_.ack_, 0, + {0x0001000000000002ULL, 0x0002000000000000ULL}); uint8_t buf[8]; rv = PR_Read(server_->ssl_fd(), buf, sizeof(buf)); EXPECT_EQ(-1, rv); @@ -800,7 +819,8 @@ TEST_P(TlsReorderDatagram13, DataAfterFinDuringZeroRtt) { CheckConnected(); EXPECT_EQ(0U, client_filters_.ack_->count()); // Acknowledgements for EOED and Finished. - CheckAcks(server_filters_, 0, {0x0001000000000002ULL, 0x0002000000000000ULL}); + CheckAcks(server_filters_.ack_, 0, + {0x0001000000000002ULL, 0x0002000000000000ULL}); uint8_t buf[8]; rv = PR_Read(server_->ssl_fd(), buf, sizeof(buf)); EXPECT_EQ(-1, rv); diff --git a/security/nss/gtests/ssl_gtest/ssl_gtest.gyp b/security/nss/gtests/ssl_gtest/ssl_gtest.gyp index be1c4ea32572..2fb6c31382db 100644 --- a/security/nss/gtests/ssl_gtest/ssl_gtest.gyp +++ b/security/nss/gtests/ssl_gtest/ssl_gtest.gyp @@ -37,6 +37,7 @@ 'ssl_loopback_unittest.cc', 'ssl_misc_unittest.cc', 'ssl_record_unittest.cc', + 'ssl_recordsep_unittest.cc', 'ssl_recordsize_unittest.cc', 'ssl_resumption_unittest.cc', 'ssl_renegotiation_unittest.cc', diff --git a/security/nss/gtests/ssl_gtest/ssl_recordsep_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_recordsep_unittest.cc new file mode 100644 index 000000000000..c54593ec85cf --- /dev/null +++ b/security/nss/gtests/ssl_gtest/ssl_recordsep_unittest.cc @@ -0,0 +1,518 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "secerr.h" +#include "ssl.h" +#include "sslerr.h" +#include "sslproto.h" + +extern "C" { +// This is not something that should make you happy. +#include "libssl_internals.h" +} + +#include +#include "gtest_utils.h" +#include "nss_scoped_ptrs.h" +#include "tls_connect.h" +#include "tls_filter.h" +#include "tls_parser.h" + +namespace nss_test { + +class HandshakeSecretTracker { + public: + HandshakeSecretTracker(const std::shared_ptr& agent, + uint16_t first_read_epoch, uint16_t first_write_epoch) + : agent_(agent), + next_read_epoch_(first_read_epoch), + next_write_epoch_(first_write_epoch) { + EXPECT_EQ(SECSuccess, + SSL_SecretCallback(agent_->ssl_fd(), + HandshakeSecretTracker::SecretCb, this)); + } + + void CheckComplete() const { + EXPECT_EQ(0, next_read_epoch_); + EXPECT_EQ(0, next_write_epoch_); + } + + private: + static void SecretCb(PRFileDesc* fd, PRUint16 epoch, SSLSecretDirection dir, + PK11SymKey* secret, void* arg) { + HandshakeSecretTracker* t = reinterpret_cast(arg); + t->SecretUpdated(epoch, dir, secret); + } + + void SecretUpdated(PRUint16 epoch, SSLSecretDirection dir, + PK11SymKey* secret) { + if (g_ssl_gtest_verbose) { + std::cerr << agent_->role_str() << ": secret callback for " << dir + << " epoch " << epoch << std::endl; + } + + EXPECT_TRUE(secret); + uint16_t* p; + if (dir == ssl_secret_read) { + p = &next_read_epoch_; + } else { + ASSERT_EQ(ssl_secret_write, dir); + p = &next_write_epoch_; + } + EXPECT_EQ(*p, epoch); + switch (*p) { + case 1: // 1 == 0-RTT, next should be handshake. + case 2: // 2 == handshake, next should be application data. + (*p)++; + break; + + case 3: // 3 == application data, there should be no more. + // Use 0 as a sentinel value. + *p = 0; + break; + + default: + ADD_FAILURE() << "Unexpected next epoch: " << *p; + } + } + + std::shared_ptr agent_; + uint16_t next_read_epoch_; + uint16_t next_write_epoch_; +}; + +TEST_F(TlsConnectTest, HandshakeSecrets) { + ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3); + EnsureTlsSetup(); + + HandshakeSecretTracker c(client_, 2, 2); + HandshakeSecretTracker s(server_, 2, 2); + + Connect(); + SendReceive(); + + c.CheckComplete(); + s.CheckComplete(); +} + +TEST_F(TlsConnectTest, ZeroRttSecrets) { + SetupForZeroRtt(); + + HandshakeSecretTracker c(client_, 2, 1); + HandshakeSecretTracker s(server_, 1, 2); + + client_->Set0RttEnabled(true); + server_->Set0RttEnabled(true); + ExpectResumption(RESUME_TICKET); + ZeroRttSendReceive(true, true); + Handshake(); + ExpectEarlyDataAccepted(true); + CheckConnected(); + SendReceive(); + + c.CheckComplete(); + s.CheckComplete(); +} + +class KeyUpdateTracker { + public: + KeyUpdateTracker(const std::shared_ptr& agent, + bool expect_read_secret) + : agent_(agent), expect_read_secret_(expect_read_secret), called_(false) { + EXPECT_EQ(SECSuccess, SSL_SecretCallback(agent_->ssl_fd(), + KeyUpdateTracker::SecretCb, this)); + } + + void CheckCalled() const { EXPECT_TRUE(called_); } + + private: + static void SecretCb(PRFileDesc* fd, PRUint16 epoch, SSLSecretDirection dir, + PK11SymKey* secret, void* arg) { + KeyUpdateTracker* t = reinterpret_cast(arg); + t->SecretUpdated(epoch, dir, secret); + } + + void SecretUpdated(PRUint16 epoch, SSLSecretDirection dir, + PK11SymKey* secret) { + EXPECT_EQ(4U, epoch); + EXPECT_EQ(expect_read_secret_, dir == ssl_secret_read); + EXPECT_TRUE(secret); + called_ = true; + } + + std::shared_ptr agent_; + bool expect_read_secret_; + bool called_; +}; + +TEST_F(TlsConnectTest, KeyUpdateSecrets) { + ConfigureVersion(SSL_LIBRARY_VERSION_TLS_1_3); + Connect(); + // The update is to the client write secret; the server read secret. + KeyUpdateTracker c(client_, false); + KeyUpdateTracker s(server_, true); + EXPECT_EQ(SECSuccess, SSL_KeyUpdate(client_->ssl_fd(), PR_FALSE)); + SendReceive(50); + SendReceive(60); + CheckEpochs(4, 3); + c.CheckCalled(); + s.CheckCalled(); +} + +// BadPrSocket is an instance of a PR IO layer that crashes the test if it is +// ever used for reading or writing. It does that by failing to overwrite any +// of the DummyIOLayerMethods, which all crash when invoked. +class BadPrSocket : public DummyIOLayerMethods { + public: + BadPrSocket(std::shared_ptr& agent) : DummyIOLayerMethods() { + static PRDescIdentity bad_identity = PR_GetUniqueIdentity("bad NSPR id"); + fd_ = DummyIOLayerMethods::CreateFD(bad_identity, this); + + // This is terrible, but NSPR doesn't provide an easy way to replace the + // bottom layer of an IO stack. Take the DummyPrSocket and replace its + // NSPR method vtable with the ones from this object. + dummy_layer_ = + PR_GetIdentitiesLayer(agent->ssl_fd(), DummyPrSocket::LayerId()); + original_methods_ = dummy_layer_->methods; + original_secret_ = dummy_layer_->secret; + dummy_layer_->methods = fd_->methods; + dummy_layer_->secret = reinterpret_cast(this); + } + + // This will be destroyed before the agent, so we need to restore the state + // before we tampered with it. + virtual ~BadPrSocket() { + dummy_layer_->methods = original_methods_; + dummy_layer_->secret = original_secret_; + } + + private: + ScopedPRFileDesc fd_; + PRFileDesc* dummy_layer_; + const PRIOMethods* original_methods_; + PRFilePrivate* original_secret_; +}; + +class StagedRecords { + public: + StagedRecords(std::shared_ptr& agent) : agent_(agent), records_() { + EXPECT_EQ(SECSuccess, + SSL_RecordLayerWriteCallback( + agent_->ssl_fd(), StagedRecords::StageRecordData, this)); + } + + virtual ~StagedRecords() { + // Uninstall so that the callback doesn't fire during cleanup. + EXPECT_EQ(SECSuccess, + SSL_RecordLayerWriteCallback(agent_->ssl_fd(), nullptr, nullptr)); + } + + bool empty() const { return records_.empty(); } + + void ForwardAll(std::shared_ptr& peer) { + EXPECT_NE(agent_, peer) << "can't forward to self"; + for (auto r : records_) { + r.Forward(peer); + } + records_.clear(); + } + + // This forwards all saved data and checks the resulting state. + void ForwardAll(std::shared_ptr& peer, + TlsAgent::State expected_state) { + ForwardAll(peer); + peer->Handshake(); + EXPECT_EQ(expected_state, peer->state()); + } + + void ForwardPartial(std::shared_ptr& peer) { + if (records_.empty()) { + ADD_FAILURE() << "No records to slice"; + return; + } + auto& last = records_.back(); + auto tail = last.SliceTail(); + ForwardAll(peer, TlsAgent::STATE_CONNECTING); + records_.push_back(tail); + EXPECT_EQ(TlsAgent::STATE_CONNECTING, peer->state()); + } + + private: + // A single record. + class StagedRecord { + public: + StagedRecord(const std::string role, uint16_t epoch, SSLContentType ct, + const uint8_t* data, size_t len) + : role_(role), epoch_(epoch), content_type_(ct), data_(data, len) { + if (g_ssl_gtest_verbose) { + std::cerr << role_ << ": staged epoch " << epoch_ << " " + << content_type_ << ": " << data_ << std::endl; + } + } + + // This forwards staged data to the identified agent. + void Forward(std::shared_ptr& peer) { + // Now there should be staged data. + EXPECT_FALSE(data_.empty()); + if (g_ssl_gtest_verbose) { + std::cerr << role_ << ": forward " << data_ << std::endl; + } + SECStatus rv = SSL_RecordLayerData( + peer->ssl_fd(), epoch_, content_type_, data_.data(), + static_cast(data_.len())); + if (rv != SECSuccess) { + EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError()); + } + } + + // Slices the tail off this record and returns it. + StagedRecord SliceTail() { + size_t slice = 1; + if (data_.len() <= slice) { + ADD_FAILURE() << "record too small to slice in two"; + slice = 0; + } + size_t keep = data_.len() - slice; + StagedRecord tail(role_, epoch_, content_type_, data_.data() + keep, + slice); + data_.Truncate(keep); + return tail; + } + + private: + std::string role_; + uint16_t epoch_; + SSLContentType content_type_; + DataBuffer data_; + }; + + // This is an SSLRecordWriteCallback that stages data. + static SECStatus StageRecordData(PRFileDesc* fd, PRUint16 epoch, + SSLContentType content_type, + const PRUint8* data, unsigned int len, + void* arg) { + auto stage = reinterpret_cast(arg); + stage->records_.push_back(StagedRecord(stage->agent_->role_str(), epoch, + content_type, data, + static_cast(len))); + return SECSuccess; + } + + std::shared_ptr& agent_; + std::deque records_; +}; + +// Attempting to feed application data in before the handshake is complete +// should be caught. +static void RefuseApplicationData(std::shared_ptr& peer, + uint16_t epoch) { + static const uint8_t d[] = {1, 2, 3}; + EXPECT_EQ(SECFailure, + SSL_RecordLayerData(peer->ssl_fd(), epoch, ssl_ct_application_data, + d, static_cast(sizeof(d)))); + EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); +} + +static void SendForwardReceive(std::shared_ptr& sender, + StagedRecords& sender_stage, + std::shared_ptr& receiver) { + const size_t count = 10; + sender->SendData(count, count); + sender_stage.ForwardAll(receiver); + receiver->ReadBytes(count); +} + +TEST_P(TlsConnectStream, ReplaceRecordLayer) { + StartConnect(); + client_->SetServerKeyBits(server_->server_key_bits()); + + // BadPrSocket installs an IO layer that crashes when the SSL layer attempts + // to read or write. + BadPrSocket bad_layer_client(client_); + BadPrSocket bad_layer_server(server_); + + // StagedRecords installs a handler for unprotected data from the socket, and + // captures that data. + StagedRecords client_stage(client_); + StagedRecords server_stage(server_); + + // Both peers should refuse application data from epoch 0. + RefuseApplicationData(client_, 0); + RefuseApplicationData(server_, 0); + + // This first call forwards nothing, but it causes the client to handshake, + // which starts things off. This stages the ClientHello as a result. + server_stage.ForwardAll(client_, TlsAgent::STATE_CONNECTING); + // This processes the ClientHello and stages the first server flight. + client_stage.ForwardAll(server_, TlsAgent::STATE_CONNECTING); + RefuseApplicationData(server_, 1); + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + // Process the server flight and the client is done. + server_stage.ForwardAll(client_, TlsAgent::STATE_CONNECTED); + client_stage.ForwardAll(server_, TlsAgent::STATE_CONNECTED); + } else { + server_stage.ForwardAll(client_, TlsAgent::STATE_CONNECTING); + RefuseApplicationData(client_, 1); + client_stage.ForwardAll(server_, TlsAgent::STATE_CONNECTED); + server_stage.ForwardAll(client_, TlsAgent::STATE_CONNECTED); + } + CheckKeys(); + + // Reading and writing application data should work. + SendForwardReceive(client_, client_stage, server_); + SendForwardReceive(server_, server_stage, client_); +} + +static SECStatus AuthCompleteBlock(TlsAgent*, PRBool, PRBool) { + return SECWouldBlock; +} + +TEST_P(TlsConnectStream, ReplaceRecordLayerAsyncLateAuth) { + StartConnect(); + client_->SetServerKeyBits(server_->server_key_bits()); + + BadPrSocket bad_layer_client(client_); + BadPrSocket bad_layer_server(server_); + StagedRecords client_stage(client_); + StagedRecords server_stage(server_); + + client_->SetAuthCertificateCallback(AuthCompleteBlock); + + server_stage.ForwardAll(client_, TlsAgent::STATE_CONNECTING); + client_stage.ForwardAll(server_, TlsAgent::STATE_CONNECTING); + server_stage.ForwardAll(client_, TlsAgent::STATE_CONNECTING); + + // Prior to TLS 1.3, the client sends its second flight immediately. But in + // TLS 1.3, a client won't send a Finished until it is happy with the server + // certificate. So blocking certificate validation causes the client to send + // nothing. + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + ASSERT_TRUE(client_stage.empty()); + + // Client should have stopped reading when it saw the Certificate message, + // so it will be reading handshake epoch, and writing cleartext. + client_->CheckEpochs(2, 0); + // Server should be reading handshake, and writing application data. + server_->CheckEpochs(2, 3); + + // Handshake again and the client will read the remainder of the server's + // flight, but it will remain blocked. + client_->Handshake(); + ASSERT_TRUE(client_stage.empty()); + EXPECT_EQ(TlsAgent::STATE_CONNECTING, client_->state()); + } else { + // In prior versions, the client's second flight is always sent. + ASSERT_FALSE(client_stage.empty()); + } + + // Now declare the certificate good. + EXPECT_EQ(SECSuccess, SSL_AuthCertificateComplete(client_->ssl_fd(), 0)); + client_->Handshake(); + ASSERT_FALSE(client_stage.empty()); + + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + EXPECT_EQ(TlsAgent::STATE_CONNECTED, client_->state()); + client_stage.ForwardAll(server_, TlsAgent::STATE_CONNECTED); + } else { + client_stage.ForwardAll(server_, TlsAgent::STATE_CONNECTED); + server_stage.ForwardAll(client_, TlsAgent::STATE_CONNECTED); + } + CheckKeys(); + + // Reading and writing application data should work. + SendForwardReceive(client_, client_stage, server_); +} + +// This test ensures that data is correctly forwarded when the handshake is +// resumed after asynchronous server certificate authentication, when +// SSL_AuthCertificateComplete() is called. The logic for resuming the +// handshake involves a different code path than the usual one, so this test +// exercises that code fully. +TEST_F(TlsConnectStreamTls13, ReplaceRecordLayerAsyncEarlyAuth) { + StartConnect(); + client_->SetServerKeyBits(server_->server_key_bits()); + + BadPrSocket bad_layer_client(client_); + BadPrSocket bad_layer_server(server_); + StagedRecords client_stage(client_); + StagedRecords server_stage(server_); + + client_->SetAuthCertificateCallback(AuthCompleteBlock); + + server_stage.ForwardAll(client_, TlsAgent::STATE_CONNECTING); + client_stage.ForwardAll(server_, TlsAgent::STATE_CONNECTING); + + // Send a partial flight on to the client. + // This includes enough to trigger the certificate callback. + server_stage.ForwardPartial(client_); + EXPECT_TRUE(client_stage.empty()); + + // Declare the certificate good. + EXPECT_EQ(SECSuccess, SSL_AuthCertificateComplete(client_->ssl_fd(), 0)); + client_->Handshake(); + EXPECT_TRUE(client_stage.empty()); + + // Send the remainder of the server flight. + PRBool pending = PR_FALSE; + EXPECT_EQ(SECSuccess, + SSLInt_HasPendingHandshakeData(client_->ssl_fd(), &pending)); + EXPECT_EQ(PR_TRUE, pending); + EXPECT_EQ(TlsAgent::STATE_CONNECTING, client_->state()); + server_stage.ForwardAll(client_, TlsAgent::STATE_CONNECTED); + client_stage.ForwardAll(server_, TlsAgent::STATE_CONNECTED); + CheckKeys(); + + SendForwardReceive(server_, server_stage, client_); +} + +TEST_P(TlsConnectStream, ForwardDataFromWrongEpoch) { + const uint8_t data[] = {1}; + Connect(); + uint16_t next_epoch; + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + EXPECT_EQ(SECFailure, + SSL_RecordLayerData(client_->ssl_fd(), 2, ssl_ct_application_data, + data, sizeof(data))); + EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()) + << "Passing data from an old epoch is rejected"; + next_epoch = 4; + } else { + // Prior to TLS 1.3, the epoch is only updated once during the handshake. + next_epoch = 2; + } + EXPECT_EQ(SECFailure, + SSL_RecordLayerData(client_->ssl_fd(), next_epoch, + ssl_ct_application_data, data, sizeof(data))); + EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError()) + << "Passing data from a future epoch blocks"; +} + +TEST_F(TlsConnectStreamTls13, ForwardInvalidData) { + const uint8_t data[1] = {0}; + + EnsureTlsSetup(); + // Zero-length data. + EXPECT_EQ(SECFailure, SSL_RecordLayerData(client_->ssl_fd(), 0, + ssl_ct_application_data, data, 0)); + EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); + + // NULL data. + EXPECT_EQ(SECFailure, + SSL_RecordLayerData(client_->ssl_fd(), 0, ssl_ct_application_data, + nullptr, 1)); + EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); +} + +TEST_F(TlsConnectDatagram13, ForwardDataDtls) { + EnsureTlsSetup(); + const uint8_t data[1] = {0}; + EXPECT_EQ(SECFailure, + SSL_RecordLayerData(client_->ssl_fd(), 0, ssl_ct_application_data, + data, sizeof(data))); + EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError()); +} + +} // namespace nss_test diff --git a/security/nss/gtests/ssl_gtest/ssl_recordsize_unittest.cc b/security/nss/gtests/ssl_gtest/ssl_recordsize_unittest.cc index c9149bcd9479..5668994204a4 100644 --- a/security/nss/gtests/ssl_gtest/ssl_recordsize_unittest.cc +++ b/security/nss/gtests/ssl_gtest/ssl_recordsize_unittest.cc @@ -123,9 +123,11 @@ TEST_P(TlsConnectGeneric, RecordSizeMaximum) { EnsureTlsSetup(); auto client_max = MakeTlsFilter(client_); - client_max->EnableDecryption(); auto server_max = MakeTlsFilter(server_); - server_max->EnableDecryption(); + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + client_max->EnableDecryption(); + server_max->EnableDecryption(); + } Connect(); client_->SendData(send_size, send_size); @@ -140,7 +142,9 @@ TEST_P(TlsConnectGeneric, RecordSizeMaximum) { TEST_P(TlsConnectGeneric, RecordSizeMinimumClient) { EnsureTlsSetup(); auto server_max = MakeTlsFilter(server_); - server_max->EnableDecryption(); + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + server_max->EnableDecryption(); + } client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64); Connect(); @@ -152,7 +156,9 @@ TEST_P(TlsConnectGeneric, RecordSizeMinimumClient) { TEST_P(TlsConnectGeneric, RecordSizeMinimumServer) { EnsureTlsSetup(); auto client_max = MakeTlsFilter(client_); - client_max->EnableDecryption(); + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + client_max->EnableDecryption(); + } server_->SetOption(SSL_RECORD_SIZE_LIMIT, 64); Connect(); @@ -164,9 +170,11 @@ TEST_P(TlsConnectGeneric, RecordSizeMinimumServer) { TEST_P(TlsConnectGeneric, RecordSizeAsymmetric) { EnsureTlsSetup(); auto client_max = MakeTlsFilter(client_); - client_max->EnableDecryption(); auto server_max = MakeTlsFilter(server_); - server_max->EnableDecryption(); + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + client_max->EnableDecryption(); + server_max->EnableDecryption(); + } client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64); server_->SetOption(SSL_RECORD_SIZE_LIMIT, 100); @@ -256,9 +264,11 @@ class TlsRecordPadder : public TlsRecordFilter { return KEEP; } + uint16_t protection_epoch; uint8_t inner_content_type; DataBuffer plaintext; - if (!Unprotect(header, record, &inner_content_type, &plaintext)) { + if (!Unprotect(header, record, &protection_epoch, &inner_content_type, + &plaintext)) { return KEEP; } @@ -267,8 +277,8 @@ class TlsRecordPadder : public TlsRecordFilter { } DataBuffer ciphertext; - bool ok = - Protect(header, inner_content_type, plaintext, &ciphertext, padding_); + bool ok = Protect(spec(protection_epoch), header, inner_content_type, + plaintext, &ciphertext, padding_); EXPECT_TRUE(ok); if (!ok) { return KEEP; @@ -334,7 +344,9 @@ TEST_P(TlsConnectGeneric, RecordSizeCapExtensionClient) { client_->SetOption(SSL_RECORD_SIZE_LIMIT, 16385); auto capture = MakeTlsFilter(client_, ssl_record_size_limit_xtn); - capture->EnableDecryption(); + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + capture->EnableDecryption(); + } Connect(); uint64_t val = 0; @@ -352,7 +364,9 @@ TEST_P(TlsConnectGeneric, RecordSizeCapExtensionServer) { server_->SetOption(SSL_RECORD_SIZE_LIMIT, 16385); auto capture = MakeTlsFilter(server_, ssl_record_size_limit_xtn); - capture->EnableDecryption(); + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + capture->EnableDecryption(); + } Connect(); uint64_t val = 0; @@ -393,7 +407,9 @@ TEST_P(TlsConnectGeneric, RecordSizeServerExtensionInvalid) { static const uint8_t v[] = {0xf4, 0x1f}; auto replace = MakeTlsFilter( server_, ssl_record_size_limit_xtn, DataBuffer(v, sizeof(v))); - replace->EnableDecryption(); + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + replace->EnableDecryption(); + } ConnectExpectAlert(client_, kTlsAlertIllegalParameter); } @@ -403,7 +419,9 @@ TEST_P(TlsConnectGeneric, RecordSizeServerExtensionExtra) { static const uint8_t v[] = {0x01, 0x00, 0x00}; auto replace = MakeTlsFilter( server_, ssl_record_size_limit_xtn, DataBuffer(v, sizeof(v))); - replace->EnableDecryption(); + if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) { + replace->EnableDecryption(); + } ConnectExpectAlert(client_, kTlsAlertDecodeError); } diff --git a/security/nss/gtests/ssl_gtest/test_io.cc b/security/nss/gtests/ssl_gtest/test_io.cc index 6d792c520c49..4a7f9145993e 100644 --- a/security/nss/gtests/ssl_gtest/test_io.cc +++ b/security/nss/gtests/ssl_gtest/test_io.cc @@ -25,10 +25,13 @@ namespace nss_test { if (g_ssl_gtest_verbose) LOG(a); \ } while (false) +PRDescIdentity DummyPrSocket::LayerId() { + static PRDescIdentity id = PR_GetUniqueIdentity("dummysocket"); + return id; +} + ScopedPRFileDesc DummyPrSocket::CreateFD() { - static PRDescIdentity test_fd_identity = - PR_GetUniqueIdentity("testtransportadapter"); - return DummyIOLayerMethods::CreateFD(test_fd_identity, this); + return DummyIOLayerMethods::CreateFD(DummyPrSocket::LayerId(), this); } void DummyPrSocket::Reset() { @@ -136,19 +139,18 @@ int32_t DummyPrSocket::Write(PRFileDesc *f, const void *buf, int32_t length) { DataBuffer filtered; PacketFilter::Action action = PacketFilter::KEEP; if (filter_) { + LOGV("Original packet: " << packet); action = filter_->Process(packet, &filtered); } switch (action) { case PacketFilter::CHANGE: - LOG("Original packet: " << packet); LOG("Filtered packet: " << filtered); dst->PacketReceived(filtered); break; case PacketFilter::DROP: - LOG("Droppped packet: " << packet); + LOG("Drop packet"); break; case PacketFilter::KEEP: - LOGV("Packet: " << packet); dst->PacketReceived(packet); break; } diff --git a/security/nss/gtests/ssl_gtest/test_io.h b/security/nss/gtests/ssl_gtest/test_io.h index 062ae86c8b4f..8efd2305b042 100644 --- a/security/nss/gtests/ssl_gtest/test_io.h +++ b/security/nss/gtests/ssl_gtest/test_io.h @@ -68,6 +68,8 @@ class DummyPrSocket : public DummyIOLayerMethods { write_error_(0) {} virtual ~DummyPrSocket() {} + static PRDescIdentity LayerId(); + // Create a file descriptor that will reference this object. The fd must not // live longer than this adapter; call PR_Close() before. ScopedPRFileDesc CreateFD(); diff --git a/security/nss/gtests/ssl_gtest/tls_agent.cc b/security/nss/gtests/ssl_gtest/tls_agent.cc index fb66196b5bb2..e726c6a61aef 100644 --- a/security/nss/gtests/ssl_gtest/tls_agent.cc +++ b/security/nss/gtests/ssl_gtest/tls_agent.cc @@ -640,6 +640,16 @@ void TlsAgent::CheckAlpn(SSLNextProtoState expected_state, } } +void TlsAgent::CheckEpochs(uint16_t expected_read, + uint16_t expected_write) const { + uint16_t read_epoch = 0; + uint16_t write_epoch = 0; + EXPECT_EQ(SECSuccess, + SSL_GetCurrentEpoch(ssl_fd(), &read_epoch, &write_epoch)); + EXPECT_EQ(expected_read, read_epoch) << role_str() << " read epoch"; + EXPECT_EQ(expected_write, write_epoch) << role_str() << " write epoch"; +} + void TlsAgent::EnableSrtp() { EXPECT_TRUE(EnsureTlsSetup()); const uint16_t ciphers[] = {SRTP_AES128_CM_HMAC_SHA1_80, diff --git a/security/nss/gtests/ssl_gtest/tls_agent.h b/security/nss/gtests/ssl_gtest/tls_agent.h index 02022186823b..a2ffe7711c36 100644 --- a/security/nss/gtests/ssl_gtest/tls_agent.h +++ b/security/nss/gtests/ssl_gtest/tls_agent.h @@ -139,6 +139,7 @@ class TlsAgent : public PollTarget { const std::string& expected = "") const; void EnableSrtp(); void CheckSrtp() const; + void CheckEpochs(uint16_t expected_read, uint16_t expected_write) const; void CheckErrorCode(int32_t expected) const; void WaitForErrorCode(int32_t expected, uint32_t delay) const; // Send data on the socket, encrypting it. diff --git a/security/nss/gtests/ssl_gtest/tls_connect.cc b/security/nss/gtests/ssl_gtest/tls_connect.cc index c48ae38ecf66..d992041df81c 100644 --- a/security/nss/gtests/ssl_gtest/tls_connect.cc +++ b/security/nss/gtests/ssl_gtest/tls_connect.cc @@ -167,18 +167,8 @@ void TlsConnectTestBase::CheckShares( void TlsConnectTestBase::CheckEpochs(uint16_t client_epoch, uint16_t server_epoch) const { - uint16_t read_epoch = 0; - uint16_t write_epoch = 0; - - EXPECT_EQ(SECSuccess, - SSLInt_GetEpochs(client_->ssl_fd(), &read_epoch, &write_epoch)); - EXPECT_EQ(server_epoch, read_epoch) << "client read epoch"; - EXPECT_EQ(client_epoch, write_epoch) << "client write epoch"; - - EXPECT_EQ(SECSuccess, - SSLInt_GetEpochs(server_->ssl_fd(), &read_epoch, &write_epoch)); - EXPECT_EQ(client_epoch, read_epoch) << "server read epoch"; - EXPECT_EQ(server_epoch, write_epoch) << "server write epoch"; + client_->CheckEpochs(server_epoch, client_epoch); + server_->CheckEpochs(client_epoch, server_epoch); } void TlsConnectTestBase::ClearStats() { diff --git a/security/nss/gtests/ssl_gtest/tls_filter.cc b/security/nss/gtests/ssl_gtest/tls_filter.cc index 25ad606fcc18..b2917274b495 100644 --- a/security/nss/gtests/ssl_gtest/tls_filter.cc +++ b/security/nss/gtests/ssl_gtest/tls_filter.cc @@ -45,40 +45,65 @@ void TlsVersioned::WriteStream(std::ostream& stream) const { } } -void TlsRecordFilter::EnableDecryption() { - SSLInt_SetCipherSpecChangeFunc(agent()->ssl_fd(), CipherSpecChanged, - (void*)this); +TlsRecordFilter::TlsRecordFilter(const std::shared_ptr& a) + : agent_(a) { + cipher_specs_.emplace_back(a->variant() == ssl_variant_datagram, 0); } -void TlsRecordFilter::CipherSpecChanged(void* arg, PRBool sending, - ssl3CipherSpec* newSpec) { - TlsRecordFilter* self = static_cast(arg); - PRBool isServer = self->agent()->role() == TlsAgent::SERVER; +void TlsRecordFilter::EnableDecryption() { + EXPECT_EQ(SECSuccess, + SSL_SecretCallback(agent()->ssl_fd(), SecretCallback, this)); + decrypting_ = true; +} +void TlsRecordFilter::SecretCallback(PRFileDesc* fd, PRUint16 epoch, + SSLSecretDirection dir, PK11SymKey* secret, + void* arg) { + TlsRecordFilter* self = static_cast(arg); if (g_ssl_gtest_verbose) { - std::cerr << (isServer ? "server" : "client") << ": " - << (sending ? "send" : "receive") - << " cipher spec changed: " << newSpec->epoch << " (" - << newSpec->phase << ")" << std::endl; + std::cerr << self->agent()->role_str() << ": " << dir + << " secret changed for epoch " << epoch << std::endl; } - if (!sending) { + + if (dir == ssl_secret_read) { return; } - uint64_t seq_no; - if (self->agent()->variant() == ssl_variant_datagram) { - seq_no = static_cast(SSLInt_CipherSpecToEpoch(newSpec)) << 48; - } else { - seq_no = 0; + for (auto& spec : self->cipher_specs_) { + ASSERT_NE(spec.epoch(), epoch) << "duplicate spec for epoch " << epoch; } - self->in_sequence_number_ = seq_no; - self->out_sequence_number_ = seq_no; - self->dropped_record_ = false; - self->cipher_spec_.reset(new TlsCipherSpec()); - bool ret = self->cipher_spec_->Init( - SSLInt_CipherSpecToEpoch(newSpec), SSLInt_CipherSpecToAlgorithm(newSpec), - SSLInt_CipherSpecToKey(newSpec), SSLInt_CipherSpecToIv(newSpec)); - EXPECT_EQ(true, ret); + + SSLPreliminaryChannelInfo preinfo; + EXPECT_EQ(SECSuccess, + SSL_GetPreliminaryChannelInfo(self->agent()->ssl_fd(), &preinfo, + sizeof(preinfo))); + EXPECT_EQ(sizeof(preinfo), preinfo.length); + + // Check the version. + if (preinfo.valuesSet & ssl_preinfo_version) { + EXPECT_EQ(SSL_LIBRARY_VERSION_TLS_1_3, preinfo.protocolVersion); + } else { + EXPECT_EQ(1U, epoch); + } + + uint16_t suite; + if (epoch == 1) { + // 0-RTT + EXPECT_TRUE(preinfo.valuesSet & ssl_preinfo_0rtt_cipher_suite); + suite = preinfo.zeroRttCipherSuite; + } else { + EXPECT_TRUE(preinfo.valuesSet & ssl_preinfo_cipher_suite); + suite = preinfo.cipherSuite; + } + + SSLCipherSuiteInfo cipherinfo; + EXPECT_EQ(SECSuccess, + SSL_GetCipherSuiteInfo(suite, &cipherinfo, sizeof(cipherinfo))); + EXPECT_EQ(sizeof(cipherinfo), cipherinfo.length); + + bool is_dtls = self->agent()->variant() == ssl_variant_datagram; + self->cipher_specs_.emplace_back(is_dtls, epoch); + EXPECT_TRUE(self->cipher_specs_.back().SetKeys(&cipherinfo, secret)); } bool TlsRecordFilter::is_dtls13() const { @@ -95,6 +120,23 @@ bool TlsRecordFilter::is_dtls13() const { info.canSendEarlyData; } +// Gets the cipher spec that matches the specified epoch. +TlsCipherSpec& TlsRecordFilter::spec(uint16_t write_epoch) { + for (auto& sp : cipher_specs_) { + if (sp.epoch() == write_epoch) { + return sp; + } + } + + // If we aren't decrypting, provide a cipher spec that does nothing other than + // count sequence numbers. + EXPECT_FALSE(decrypting_) << "No spec available for epoch " << write_epoch; + ; + bool is_dtls = agent()->variant() == ssl_variant_datagram; + cipher_specs_.emplace_back(is_dtls, write_epoch); + return cipher_specs_.back(); +} + PacketFilter::Action TlsRecordFilter::Filter(const DataBuffer& input, DataBuffer* output) { // Disable during shutdown. @@ -108,34 +150,28 @@ PacketFilter::Action TlsRecordFilter::Filter(const DataBuffer& input, output->Allocate(input.len()); TlsParser parser(input); + // This uses the current write spec for the purposes of parsing the epoch and + // sequence number from the header. This might be wrong because we can + // receive records from older specs, but guessing is good enough: + // - In DTLS, parsing the sequence number corrects any errors. + // - In TLS, we don't use the sequence number unless decrypting, where we use + // trial decryption to get the right epoch. + uint16_t write_epoch = 0; + SECStatus rv = SSL_GetCurrentEpoch(agent()->ssl_fd(), nullptr, &write_epoch); + if (rv != SECSuccess) { + ADD_FAILURE() << "unable to read epoch"; + return KEEP; + } + uint64_t guess_seqno = static_cast(write_epoch) << 48; + while (parser.remaining()) { TlsRecordHeader header; DataBuffer record; - - if (!header.Parse(is_dtls13(), in_sequence_number_, &parser, &record)) { + if (!header.Parse(is_dtls13(), guess_seqno, &parser, &record)) { ADD_FAILURE() << "not a valid record"; return KEEP; } - // Track the sequence number, which is necessary for stream mode when - // decrypting and for TLS 1.3 datagram to recover the sequence number. - // - // We reset the counter when the cipher spec changes, but that notification - // appears before a record is sent. If multiple records are sent with - // different cipher specs, this would fail. This filters out cleartext - // records, so we don't get confused by handshake messages that are sent at - // the same time as encrypted records. Sequence numbers are therefore - // likely to be incorrect for cleartext records. - // - // This isn't perfectly robust: if there is a change from an active cipher - // spec to another active cipher spec (KeyUpdate for instance) AND writes - // are consolidated across that change, this code could use the wrong - // sequence numbers when re-encrypting records with the old keys. - if (header.content_type() == ssl_ct_application_data) { - in_sequence_number_ = - (std::max)(in_sequence_number_, header.sequence_number() + 1); - } - if (FilterRecord(header, record, &offset, output) != KEEP) { changed = true; } else { @@ -159,14 +195,16 @@ PacketFilter::Action TlsRecordFilter::FilterRecord( DataBuffer filtered; uint8_t inner_content_type; DataBuffer plaintext; + uint16_t protection_epoch = 0; - if (!Unprotect(header, record, &inner_content_type, &plaintext)) { - if (g_ssl_gtest_verbose) { - std::cerr << "unprotect failed: " << header << ":" << record << std::endl; - } + if (!Unprotect(header, record, &protection_epoch, &inner_content_type, + &plaintext)) { + std::cerr << agent()->role_str() << ": unprotect failed: " << header << ":" + << record << std::endl; return KEEP; } + auto& protection_spec = spec(protection_epoch); TlsRecordHeader real_header(header.variant(), header.version(), inner_content_type, header.sequence_number()); @@ -174,7 +212,9 @@ PacketFilter::Action TlsRecordFilter::FilterRecord( // In stream mode, even if something doesn't change we need to re-encrypt if // previous packets were dropped. if (action == KEEP) { - if (header.is_dtls() || !dropped_record_) { + if (header.is_dtls() || !protection_spec.record_dropped()) { + // Count every outgoing packet. + protection_spec.RecordProtected(); return KEEP; } filtered = plaintext; @@ -182,7 +222,7 @@ PacketFilter::Action TlsRecordFilter::FilterRecord( if (action == DROP) { std::cerr << "record drop: " << header << ":" << record << std::endl; - dropped_record_ = true; + protection_spec.RecordDropped(); return DROP; } @@ -192,19 +232,18 @@ PacketFilter::Action TlsRecordFilter::FilterRecord( std::cerr << "record new: " << filtered << std::endl; } - uint64_t seq_num; - if (header.is_dtls() || !cipher_spec_ || - header.content_type() != ssl_ct_application_data) { - seq_num = header.sequence_number(); - } else { - seq_num = out_sequence_number_++; + uint64_t seq_num = protection_spec.next_out_seqno(); + if (!decrypting_ && header.is_dtls()) { + // Copy over the epoch, which isn't tracked when not decrypting. + seq_num |= header.sequence_number() & (0xffffULL << 48); } + TlsRecordHeader out_header(header.variant(), header.version(), header.content_type(), seq_num); DataBuffer ciphertext; - bool rv = Protect(out_header, inner_content_type, filtered, &ciphertext); - EXPECT_TRUE(rv); + bool rv = Protect(protection_spec, out_header, inner_content_type, filtered, + &ciphertext); if (!rv) { return KEEP; } @@ -227,15 +266,20 @@ uint64_t TlsRecordHeader::RecoverSequenceNumber(uint64_t expected, uint32_t partial, size_t partial_bits) { EXPECT_GE(32U, partial_bits); - uint64_t mask = (1 << partial_bits) - 1; + uint64_t mask = (1ULL << partial_bits) - 1; // First we determine the highest possible value. This is half the - // expressible range above the expected value. - uint64_t cap = expected + (1ULL << (partial_bits - 1)); + // expressible range above the expected value, less 1. + // + // We subtract the extra 1 from the cap so that when given a choice between + // the equidistant expected+N and expected-N we want to chose the lower. With + // 0-RTT, we sometimes have to recover an epoch of 1 when we expect an epoch + // of 3 and with 2 partial bits, the alternative result of 5 is wrong. + uint64_t cap = expected + (1ULL << (partial_bits - 1)) - 1; // Add the partial piece in. e.g., xxxx789a and 1234 becomes xxxx1234. uint64_t seq_no = (cap & ~mask) | partial; // If the partial value is higher than the same partial piece from the cap, // then the real value has to be lower. e.g., xxxx1234 can't become xxxx5678. - if (partial > (cap & mask)) { + if (partial > (cap & mask) && (seq_no >= (1ULL << partial_bits))) { seq_no -= 1ULL << partial_bits; } return seq_no; @@ -375,16 +419,41 @@ size_t TlsRecordHeader::Write(DataBuffer* buffer, size_t offset, bool TlsRecordFilter::Unprotect(const TlsRecordHeader& header, const DataBuffer& ciphertext, + uint16_t* protection_epoch, uint8_t* inner_content_type, DataBuffer* plaintext) { - if (!cipher_spec_ || header.content_type() != ssl_ct_application_data) { + if (!decrypting_ || header.content_type() != ssl_ct_application_data) { + // Maintain the epoch and sequence number for plaintext records. + uint16_t ep = 0; + if (agent()->variant() == ssl_variant_datagram) { + ep = static_cast(header.sequence_number() >> 48); + } + spec(ep).RecordUnprotected(header.sequence_number()); + *protection_epoch = ep; *inner_content_type = header.content_type(); *plaintext = ciphertext; return true; } - if (!cipher_spec_->Unprotect(header, ciphertext, plaintext)) { - return false; + uint16_t ep = 0; + if (agent()->variant() == ssl_variant_datagram) { + ep = static_cast(header.sequence_number() >> 48); + if (!spec(ep).Unprotect(header, ciphertext, plaintext)) { + return false; + } + } else { + // In TLS, records aren't clearly labelled with their epoch, and we + // can't just use the newest keys because the same flight of messages can + // contain multiple epochs. So... trial decrypt! + for (size_t i = cipher_specs_.size() - 1; i > 0; --i) { + if (cipher_specs_[i].Unprotect(header, ciphertext, plaintext)) { + ep = cipher_specs_[i].epoch(); + break; + } + } + if (!ep) { + return false; + } } size_t len = plaintext->len(); @@ -396,33 +465,45 @@ bool TlsRecordFilter::Unprotect(const TlsRecordHeader& header, return false; } + *protection_epoch = ep; *inner_content_type = plaintext->data()[len - 1]; plaintext->Truncate(len - 1); if (g_ssl_gtest_verbose) { - std::cerr << "unprotect: " << std::hex << header.sequence_number() - << std::dec << " type=" << static_cast(*inner_content_type) + std::cerr << agent()->role_str() << ": unprotect: epoch=" << ep + << " seq=" << std::hex << header.sequence_number() << std::dec << " " << *plaintext << std::endl; } return true; } -bool TlsRecordFilter::Protect(const TlsRecordHeader& header, +bool TlsRecordFilter::Protect(TlsCipherSpec& protection_spec, + const TlsRecordHeader& header, uint8_t inner_content_type, const DataBuffer& plaintext, DataBuffer* ciphertext, size_t padding) { - if (!cipher_spec_ || header.content_type() != ssl_ct_application_data) { + if (!protection_spec.is_protected()) { + // Not protected, just keep the sequence numbers updated. + protection_spec.RecordProtected(); *ciphertext = plaintext; return true; } - if (g_ssl_gtest_verbose) { - std::cerr << "protect: " << header.sequence_number() << std::endl; - } + DataBuffer padded; padded.Allocate(plaintext.len() + 1 + padding); size_t offset = padded.Write(0, plaintext.data(), plaintext.len()); padded.Write(offset, inner_content_type, 1); - return cipher_spec_->Protect(header, padded, ciphertext); + + bool ok = protection_spec.Protect(header, padded, ciphertext); + if (!ok) { + ADD_FAILURE() << "protect fail"; + } else if (g_ssl_gtest_verbose) { + std::cerr << agent()->role_str() + << ": protect: epoch=" << protection_spec.epoch() + << " seq=" << std::hex << header.sequence_number() << std::dec + << " " << *ciphertext << std::endl; + } + return ok; } bool IsHelloRetry(const DataBuffer& body) { diff --git a/security/nss/gtests/ssl_gtest/tls_filter.h b/security/nss/gtests/ssl_gtest/tls_filter.h index 2b6e8864568a..9c75ae960b45 100644 --- a/security/nss/gtests/ssl_gtest/tls_filter.h +++ b/security/nss/gtests/ssl_gtest/tls_filter.h @@ -97,13 +97,7 @@ inline std::shared_ptr MakeTlsFilter(const std::shared_ptr& agent, // Abstract filter that operates on entire (D)TLS records. class TlsRecordFilter : public PacketFilter { public: - TlsRecordFilter(const std::shared_ptr& a) - : agent_(a), - count_(0), - cipher_spec_(), - dropped_record_(false), - in_sequence_number_(0), - out_sequence_number_(0) {} + TlsRecordFilter(const std::shared_ptr& a); std::shared_ptr agent() const { return agent_.lock(); } @@ -118,10 +112,11 @@ class TlsRecordFilter : public PacketFilter { // behavior. void EnableDecryption(); bool Unprotect(const TlsRecordHeader& header, const DataBuffer& cipherText, - uint8_t* inner_content_type, DataBuffer* plaintext); - bool Protect(const TlsRecordHeader& header, uint8_t inner_content_type, - const DataBuffer& plaintext, DataBuffer* ciphertext, - size_t padding = 0); + uint16_t* protection_epoch, uint8_t* inner_content_type, + DataBuffer* plaintext); + bool Protect(TlsCipherSpec& protection_spec, const TlsRecordHeader& header, + uint8_t inner_content_type, const DataBuffer& plaintext, + DataBuffer* ciphertext, size_t padding = 0); protected: // There are two filter functions which can be overriden. Both are @@ -146,20 +141,17 @@ class TlsRecordFilter : public PacketFilter { } bool is_dtls13() const; + TlsCipherSpec& spec(uint16_t epoch); private: - static void CipherSpecChanged(void* arg, PRBool sending, - ssl3CipherSpec* newSpec); + static void SecretCallback(PRFileDesc* fd, PRUint16 epoch, + SSLSecretDirection dir, PK11SymKey* secret, + void* arg); std::weak_ptr agent_; - size_t count_; - std::unique_ptr cipher_spec_; - // Whether we dropped a record since the cipher spec changed. - bool dropped_record_; - // The sequence number we use for reading records as they are written. - uint64_t in_sequence_number_; - // The sequence number we use for writing modified records. - uint64_t out_sequence_number_; + size_t count_ = 0; + std::vector cipher_specs_; + bool decrypting_ = false; }; inline std::ostream& operator<<(std::ostream& stream, const TlsVersioned& v) { diff --git a/security/nss/gtests/ssl_gtest/tls_protect.cc b/security/nss/gtests/ssl_gtest/tls_protect.cc index c715a36a6bc0..c92b5ccf78b4 100644 --- a/security/nss/gtests/ssl_gtest/tls_protect.cc +++ b/security/nss/gtests/ssl_gtest/tls_protect.cc @@ -7,6 +7,9 @@ #include "tls_protect.h" #include "tls_filter.h" +// Do this to avoid having to re-implement HKDF. +#include "tls13hkdf.h" + namespace nss_test { AeadCipher::~AeadCipher() { @@ -15,32 +18,37 @@ AeadCipher::~AeadCipher() { } } -bool AeadCipher::Init(PK11SymKey *key, const uint8_t *iv) { - key_ = PK11_ReferenceSymKey(key); +bool AeadCipher::Init(PK11SymKey* key, const uint8_t* iv) { + key_ = key; if (!key_) return false; memcpy(iv_, iv, sizeof(iv_)); + if (g_ssl_gtest_verbose) { + EXPECT_EQ(SECSuccess, PK11_ExtractKeyValue(key_)); + SECItem* raw_key = PK11_GetKeyData(key_); + std::cerr << "key: " << DataBuffer(raw_key->data, raw_key->len) + << std::endl; + std::cerr << "iv: " << DataBuffer(iv_, 12) << std::endl; + } return true; } -void AeadCipher::FormatNonce(uint64_t seq, uint8_t *nonce) { +void AeadCipher::FormatNonce(uint64_t seq, uint8_t* nonce) { memcpy(nonce, iv_, 12); for (size_t i = 0; i < 8; ++i) { nonce[12 - (i + 1)] ^= seq & 0xff; seq >>= 8; } - - DataBuffer d(nonce, 12); } -bool AeadCipher::AeadInner(bool decrypt, void *params, size_t param_length, - const uint8_t *in, size_t inlen, uint8_t *out, - size_t *outlen, size_t maxlen) { +bool AeadCipher::AeadInner(bool decrypt, void* params, size_t param_length, + const uint8_t* in, size_t inlen, uint8_t* out, + size_t* outlen, size_t maxlen) { SECStatus rv; unsigned int uoutlen = 0; SECItem param = { - siBuffer, static_cast(params), + siBuffer, static_cast(params), static_cast(param_length), }; @@ -54,28 +62,28 @@ bool AeadCipher::AeadInner(bool decrypt, void *params, size_t param_length, return rv == SECSuccess; } -bool AeadCipherAesGcm::Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len, - uint64_t seq, const uint8_t *in, size_t inlen, - uint8_t *out, size_t *outlen, size_t maxlen) { +bool AeadCipherAesGcm::Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len, + uint64_t seq, const uint8_t* in, size_t inlen, + uint8_t* out, size_t* outlen, size_t maxlen) { CK_GCM_PARAMS aeadParams; unsigned char nonce[12]; memset(&aeadParams, 0, sizeof(aeadParams)); aeadParams.pIv = nonce; aeadParams.ulIvLen = sizeof(nonce); - aeadParams.pAAD = const_cast(hdr); + aeadParams.pAAD = const_cast(hdr); aeadParams.ulAADLen = hdr_len; aeadParams.ulTagBits = 128; FormatNonce(seq, nonce); - return AeadInner(decrypt, (unsigned char *)&aeadParams, sizeof(aeadParams), - in, inlen, out, outlen, maxlen); + return AeadInner(decrypt, (unsigned char*)&aeadParams, sizeof(aeadParams), in, + inlen, out, outlen, maxlen); } -bool AeadCipherChacha20Poly1305::Aead(bool decrypt, const uint8_t *hdr, +bool AeadCipherChacha20Poly1305::Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len, uint64_t seq, - const uint8_t *in, size_t inlen, - uint8_t *out, size_t *outlen, + const uint8_t* in, size_t inlen, + uint8_t* out, size_t* outlen, size_t maxlen) { CK_NSS_AEAD_PARAMS aeadParams; unsigned char nonce[12]; @@ -83,67 +91,126 @@ bool AeadCipherChacha20Poly1305::Aead(bool decrypt, const uint8_t *hdr, memset(&aeadParams, 0, sizeof(aeadParams)); aeadParams.pNonce = nonce; aeadParams.ulNonceLen = sizeof(nonce); - aeadParams.pAAD = const_cast(hdr); + aeadParams.pAAD = const_cast(hdr); aeadParams.ulAADLen = hdr_len; aeadParams.ulTagLen = 16; FormatNonce(seq, nonce); - return AeadInner(decrypt, (unsigned char *)&aeadParams, sizeof(aeadParams), - in, inlen, out, outlen, maxlen); + return AeadInner(decrypt, (unsigned char*)&aeadParams, sizeof(aeadParams), in, + inlen, out, outlen, maxlen); } -bool TlsCipherSpec::Init(uint16_t epoc, SSLCipherAlgorithm cipher, - PK11SymKey *key, const uint8_t *iv) { - epoch_ = epoc; - switch (cipher) { +static uint64_t FirstSeqno(bool dtls, uint16_t epoc) { + if (dtls) { + return static_cast(epoc) << 48; + } + return 0; +} + +TlsCipherSpec::TlsCipherSpec(bool dtls, uint16_t epoc) + : dtls_(dtls), + epoch_(epoc), + in_seqno_(FirstSeqno(dtls, epoc)), + out_seqno_(FirstSeqno(dtls, epoc)) {} + +bool TlsCipherSpec::SetKeys(SSLCipherSuiteInfo* cipherinfo, + PK11SymKey* secret) { + CK_MECHANISM_TYPE mech; + switch (cipherinfo->symCipher) { case ssl_calg_aes_gcm: aead_.reset(new AeadCipherAesGcm()); + mech = CKM_AES_GCM; break; case ssl_calg_chacha20: aead_.reset(new AeadCipherChacha20Poly1305()); + mech = CKM_NSS_CHACHA20_POLY1305; break; default: return false; } + PK11SymKey* key; + const std::string kPurposeKey = "key"; + SECStatus rv = tls13_HkdfExpandLabel( + secret, cipherinfo->kdfHash, NULL, 0, kPurposeKey.c_str(), + kPurposeKey.length(), mech, cipherinfo->symKeyBits / 8, &key); + if (rv != SECSuccess) { + ADD_FAILURE() << "unable to derive key for epoch " << epoch_; + return false; + } + + // No constant for IV length, but everything we know of uses 12. + uint8_t iv[12]; + const std::string kPurposeIv = "iv"; + rv = tls13_HkdfExpandLabelRaw(secret, cipherinfo->kdfHash, NULL, 0, + kPurposeIv.c_str(), kPurposeIv.length(), iv, + sizeof(iv)); + if (rv != SECSuccess) { + ADD_FAILURE() << "unable to derive IV for epoch " << epoch_; + return false; + } + return aead_->Init(key, iv); } -bool TlsCipherSpec::Unprotect(const TlsRecordHeader &header, - const DataBuffer &ciphertext, - DataBuffer *plaintext) { +bool TlsCipherSpec::Unprotect(const TlsRecordHeader& header, + const DataBuffer& ciphertext, + DataBuffer* plaintext) { + if (aead_ == nullptr) { + return false; + } // Make space. plaintext->Allocate(ciphertext.len()); auto header_bytes = header.header(); size_t len; - bool ret = - aead_->Aead(true, header_bytes.data(), header_bytes.len(), - header.sequence_number(), ciphertext.data(), ciphertext.len(), - plaintext->data(), &len, plaintext->len()); - if (!ret) return false; + uint64_t seqno; + if (dtls_) { + seqno = header.sequence_number(); + } else { + seqno = in_seqno_; + } + bool ret = aead_->Aead(true, header_bytes.data(), header_bytes.len(), seqno, + ciphertext.data(), ciphertext.len(), plaintext->data(), + &len, plaintext->len()); + if (!ret) { + return false; + } + RecordUnprotected(seqno); plaintext->Truncate(len); return true; } -bool TlsCipherSpec::Protect(const TlsRecordHeader &header, - const DataBuffer &plaintext, - DataBuffer *ciphertext) { +bool TlsCipherSpec::Protect(const TlsRecordHeader& header, + const DataBuffer& plaintext, + DataBuffer* ciphertext) { + if (aead_ == nullptr) { + return false; + } // Make a padded buffer. - ciphertext->Allocate(plaintext.len() + 32); // Room for any plausible auth tag size_t len; DataBuffer header_bytes; (void)header.WriteHeader(&header_bytes, 0, plaintext.len() + 16); - bool ret = - aead_->Aead(false, header_bytes.data(), header_bytes.len(), - header.sequence_number(), plaintext.data(), plaintext.len(), - ciphertext->data(), &len, ciphertext->len()); - if (!ret) return false; + uint64_t seqno; + if (dtls_) { + seqno = header.sequence_number(); + } else { + seqno = out_seqno_; + } + + bool ret = aead_->Aead(false, header_bytes.data(), header_bytes.len(), seqno, + plaintext.data(), plaintext.len(), ciphertext->data(), + &len, ciphertext->len()); + if (!ret) { + return false; + } + + RecordProtected(); ciphertext->Truncate(len); return true; diff --git a/security/nss/gtests/ssl_gtest/tls_protect.h b/security/nss/gtests/ssl_gtest/tls_protect.h index 6f129a4eb614..ded515dbbc5d 100644 --- a/security/nss/gtests/ssl_gtest/tls_protect.h +++ b/security/nss/gtests/ssl_gtest/tls_protect.h @@ -22,19 +22,19 @@ class AeadCipher { AeadCipher(CK_MECHANISM_TYPE mech) : mech_(mech), key_(nullptr) {} virtual ~AeadCipher(); - bool Init(PK11SymKey *key, const uint8_t *iv); - virtual bool Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len, - uint64_t seq, const uint8_t *in, size_t inlen, uint8_t *out, - size_t *outlen, size_t maxlen) = 0; + bool Init(PK11SymKey* key, const uint8_t* iv); + virtual bool Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len, + uint64_t seq, const uint8_t* in, size_t inlen, uint8_t* out, + size_t* outlen, size_t maxlen) = 0; protected: - void FormatNonce(uint64_t seq, uint8_t *nonce); - bool AeadInner(bool decrypt, void *params, size_t param_length, - const uint8_t *in, size_t inlen, uint8_t *out, size_t *outlen, + void FormatNonce(uint64_t seq, uint8_t* nonce); + bool AeadInner(bool decrypt, void* params, size_t param_length, + const uint8_t* in, size_t inlen, uint8_t* out, size_t* outlen, size_t maxlen); CK_MECHANISM_TYPE mech_; - PK11SymKey *key_; + PK11SymKey* key_; uint8_t iv_[12]; }; @@ -43,8 +43,8 @@ class AeadCipherChacha20Poly1305 : public AeadCipher { AeadCipherChacha20Poly1305() : AeadCipher(CKM_NSS_CHACHA20_POLY1305) {} protected: - bool Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len, uint64_t seq, - const uint8_t *in, size_t inlen, uint8_t *out, size_t *outlen, + bool Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len, uint64_t seq, + const uint8_t* in, size_t inlen, uint8_t* out, size_t* outlen, size_t maxlen); }; @@ -53,27 +53,42 @@ class AeadCipherAesGcm : public AeadCipher { AeadCipherAesGcm() : AeadCipher(CKM_AES_GCM) {} protected: - bool Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len, uint64_t seq, - const uint8_t *in, size_t inlen, uint8_t *out, size_t *outlen, + bool Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len, uint64_t seq, + const uint8_t* in, size_t inlen, uint8_t* out, size_t* outlen, size_t maxlen); }; // Our analog of ssl3CipherSpec class TlsCipherSpec { public: - TlsCipherSpec() : epoch_(0), aead_() {} + TlsCipherSpec(bool dtls, uint16_t epoc); + bool SetKeys(SSLCipherSuiteInfo* cipherinfo, PK11SymKey* secret); - bool Init(uint16_t epoch, SSLCipherAlgorithm cipher, PK11SymKey *key, - const uint8_t *iv); + bool Protect(const TlsRecordHeader& header, const DataBuffer& plaintext, + DataBuffer* ciphertext); + bool Unprotect(const TlsRecordHeader& header, const DataBuffer& ciphertext, + DataBuffer* plaintext); - bool Protect(const TlsRecordHeader &header, const DataBuffer &plaintext, - DataBuffer *ciphertext); - bool Unprotect(const TlsRecordHeader &header, const DataBuffer &ciphertext, - DataBuffer *plaintext); uint16_t epoch() const { return epoch_; } + uint64_t next_in_seqno() const { return in_seqno_; } + void RecordUnprotected(uint64_t seqno) { + // Reordering happens, so don't let this go backwards. + in_seqno_ = (std::max)(in_seqno_, seqno + 1); + } + uint64_t next_out_seqno() { return out_seqno_; } + void RecordProtected() { out_seqno_++; } + + void RecordDropped() { record_dropped_ = true; } + bool record_dropped() const { return record_dropped_; } + + bool is_protected() const { return aead_ != nullptr; } private: + bool dtls_; uint16_t epoch_; + uint64_t in_seqno_; + uint64_t out_seqno_; + bool record_dropped_ = false; std::unique_ptr aead_; }; diff --git a/security/nss/lib/pk11wrap/pk11akey.c b/security/nss/lib/pk11wrap/pk11akey.c index c6070e264db0..97adabb50443 100644 --- a/security/nss/lib/pk11wrap/pk11akey.c +++ b/security/nss/lib/pk11wrap/pk11akey.c @@ -1677,6 +1677,92 @@ PK11_MakeKEAPubKey(unsigned char *keyData, int length) return pubk; } +SECStatus +SECKEY_SetPublicValue(SECKEYPrivateKey *privKey, SECItem *publicValue) +{ + SECStatus rv; + SECKEYPublicKey pubKey; + PLArenaPool *arena; + PK11SlotInfo *slot = privKey->pkcs11Slot; + CK_OBJECT_HANDLE privKeyID = privKey->pkcs11ID; + + if (privKey == NULL || publicValue == NULL || + publicValue->data == NULL || publicValue->len == 0) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + pubKey.arena = NULL; + pubKey.keyType = privKey->keyType; + pubKey.pkcs11Slot = NULL; + pubKey.pkcs11ID = CK_INVALID_HANDLE; + /* can't use PORT_InitCheapArena here becase SECKEY_DestroyPublic is used + * to free it, and it uses PORT_FreeArena which not only frees the + * underlying arena, it also frees the allocated arena struct. */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + pubKey.arena = arena; + if (arena == NULL) { + return SECFailure; + } + rv = SECFailure; + switch (privKey->keyType) { + default: + /* error code already set to SECFailure */ + break; + case rsaKey: + pubKey.u.rsa.modulus = *publicValue; + rv = PK11_ReadAttribute(slot, privKeyID, CKA_PUBLIC_EXPONENT, + arena, &pubKey.u.rsa.publicExponent); + break; + case dsaKey: + pubKey.u.dsa.publicValue = *publicValue; + rv = PK11_ReadAttribute(slot, privKeyID, CKA_PRIME, + arena, &pubKey.u.dsa.params.prime); + if (rv != SECSuccess) { + break; + } + rv = PK11_ReadAttribute(slot, privKeyID, CKA_SUBPRIME, + arena, &pubKey.u.dsa.params.subPrime); + if (rv != SECSuccess) { + break; + } + rv = PK11_ReadAttribute(slot, privKeyID, CKA_BASE, + arena, &pubKey.u.dsa.params.base); + break; + case dhKey: + pubKey.u.dh.publicValue = *publicValue; + rv = PK11_ReadAttribute(slot, privKeyID, CKA_PRIME, + arena, &pubKey.u.dh.prime); + if (rv != SECSuccess) { + break; + } + rv = PK11_ReadAttribute(slot, privKeyID, CKA_BASE, + arena, &pubKey.u.dh.base); + break; + case ecKey: + pubKey.u.ec.publicValue = *publicValue; + pubKey.u.ec.encoding = ECPoint_Undefined; + pubKey.u.ec.size = 0; + rv = PK11_ReadAttribute(slot, privKeyID, CKA_EC_PARAMS, + arena, &pubKey.u.ec.DEREncodedParams); + break; + } + if (rv == SECSuccess) { + rv = PK11_ImportPublicKey(slot, &pubKey, PR_TRUE); + } + /* Even though pubKey is stored on the stack, we've allocated + * some of it's data from the arena. SECKEY_DestroyPublicKey + * destroys keys by freeing the arena, so this will clean up all + * the data we allocated specifically for the key above. It will + * also free any slot references which we may have picked up in + * PK11_ImportPublicKey. It won't delete the underlying key if + * its a Token/Permanent key (which it will be if + * PK11_ImportPublicKey succeeds). */ + SECKEY_DestroyPublicKey(&pubKey); + + return rv; +} + /* * NOTE: This function doesn't return a SECKEYPrivateKey struct to represent * the new private key object. If it were to create a session object that @@ -1802,12 +1888,6 @@ try_faulty_3des: nickname, publicValue, isPerm, isPrivate, key_type, usage, usageCount, wincx); if (privKey) { - if (privk) { - *privk = privKey; - } else { - SECKEY_DestroyPrivateKey(privKey); - } - privKey = NULL; rv = SECSuccess; goto done; } @@ -1837,6 +1917,25 @@ try_faulty_3des: rv = SECFailure; done: + if ((rv == SECSuccess) && isPerm) { + /* If we are importing a token object, + * create the corresponding public key. + * If this fails, just continue as the target + * token simply might not support persistant + * public keys. Such tokens are usable, but + * need to be authenticated before searching + * for user certs. */ + (void)SECKEY_SetPublicValue(privKey, publicValue); + } + + if (privKey) { + if (privk) { + *privk = privKey; + } else { + SECKEY_DestroyPrivateKey(privKey); + } + privKey = NULL; + } if (crypto_param != NULL) { SECITEM_ZfreeItem(crypto_param, PR_TRUE); } diff --git a/security/nss/lib/softoken/pkcs11.c b/security/nss/lib/softoken/pkcs11.c index 34f25a9d06e3..bb86baa69a9f 100644 --- a/security/nss/lib/softoken/pkcs11.c +++ b/security/nss/lib/softoken/pkcs11.c @@ -1815,8 +1815,6 @@ sftk_GetPubKey(SFTKObject *object, CK_KEY_TYPE key_type, break; /* key was not DER encoded, no need to unwrap */ } - PORT_Assert(pubKey->u.ec.ecParams.name != ECCurve25519); - /* handle the encoded case */ if ((pubKey->u.ec.publicValue.data[0] == SEC_ASN1_OCTET_STRING) && pubKey->u.ec.publicValue.len > keyLen) { @@ -1827,7 +1825,13 @@ sftk_GetPubKey(SFTKObject *object, CK_KEY_TYPE key_type, SEC_ASN1_GET(SEC_OctetStringTemplate), &pubKey->u.ec.publicValue); /* nope, didn't decode correctly */ - if ((rv != SECSuccess) || (publicValue.data[0] != EC_POINT_FORM_UNCOMPRESSED) || (publicValue.len != keyLen)) { + if ((rv != SECSuccess) || (publicValue.len != keyLen)) { + crv = CKR_ATTRIBUTE_VALUE_INVALID; + break; + } + /* we don't handle compressed points except in the case of ECCurve25519 */ + if ((pubKey->u.ec.ecParams.fieldID.type != ec_field_plain) && + (publicValue.data[0] != EC_POINT_FORM_UNCOMPRESSED)) { crv = CKR_ATTRIBUTE_VALUE_INVALID; break; } diff --git a/security/nss/lib/ssl/dtls13con.c b/security/nss/lib/ssl/dtls13con.c index 81d196deee4c..0c4fc7fcd1ed 100644 --- a/security/nss/lib/ssl/dtls13con.c +++ b/security/nss/lib/ssl/dtls13con.c @@ -482,7 +482,7 @@ dtls13_HandleAck(sslSocket *ss, sslBuffer *databuf) * for the holddown period to process retransmitted Finisheds. */ if (!ss->sec.isServer && (ss->ssl3.hs.ws == idle_handshake)) { - ssl_CipherSpecReleaseByEpoch(ss, CipherSpecRead, + ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read, TrafficKeyHandshake); } } @@ -509,6 +509,6 @@ dtls13_HolddownTimerCb(sslSocket *ss) { SSL_TRC(10, ("%d: SSL3[%d]: holddown timer fired", SSL_GETPID(), ss->fd)); - ssl_CipherSpecReleaseByEpoch(ss, CipherSpecRead, TrafficKeyHandshake); + ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read, TrafficKeyHandshake); ssl_ClearPRCList(&ss->ssl3.hs.dtlsRcvdHandshake, NULL); } diff --git a/security/nss/lib/ssl/dtlscon.c b/security/nss/lib/ssl/dtlscon.c index a5c604bca3c1..bbd2f6d79b4d 100644 --- a/security/nss/lib/ssl/dtlscon.c +++ b/security/nss/lib/ssl/dtlscon.c @@ -542,7 +542,6 @@ dtls_QueueMessage(sslSocket *ss, SSLContentType ct, /* Add DTLS handshake message to the pending queue * Empty the sendBuf buffer. - * This function returns SECSuccess or SECFailure, never SECWouldBlock. * Always set sendBuf.len to 0, even when returning SECFailure. * * Called from: diff --git a/security/nss/lib/ssl/ssl.h b/security/nss/lib/ssl/ssl.h index fc4a4a70cb0c..bfeddeff89be 100644 --- a/security/nss/lib/ssl/ssl.h +++ b/security/nss/lib/ssl/ssl.h @@ -299,6 +299,17 @@ SSL_IMPORT PRFileDesc *DTLS_ImportFD(PRFileDesc *model, PRFileDesc *fd); * This is disabled by default and will be removed in a future version. */ #define SSL_ENABLE_V2_COMPATIBLE_HELLO 38 +/* Enables the post-handshake authentication in TLS 1.3. If it is set + * to PR_TRUE, the client will send the "post_handshake_auth" + * extension to indicate that it will process CertificateRequest + * messages after handshake. + * + * This option applies only to clients. For a server, the + * SSL_SendCertificateRequest can be used to request post-handshake + * authentication. + */ +#define SSL_ENABLE_POST_HANDSHAKE_AUTH 39 + #ifdef SSL_DEPRECATED_FUNCTION /* Old deprecated function names */ SSL_IMPORT SECStatus SSL_Enable(PRFileDesc *fd, int option, PRIntn on); diff --git a/security/nss/lib/ssl/ssl3con.c b/security/nss/lib/ssl/ssl3con.c index 3b5c69b11459..b5b047228d33 100644 --- a/security/nss/lib/ssl/ssl3con.c +++ b/security/nss/lib/ssl/ssl3con.c @@ -1394,14 +1394,14 @@ loser: } static SECStatus -ssl3_SetupPendingCipherSpec(sslSocket *ss, CipherSpecDirection direction, +ssl3_SetupPendingCipherSpec(sslSocket *ss, SSLSecretDirection direction, const ssl3CipherSuiteDef *suiteDef, ssl3CipherSpec **specp) { ssl3CipherSpec *spec; const ssl3CipherSpec *prev; - prev = (direction == CipherSpecWrite) ? ss->ssl3.cwSpec : ss->ssl3.crSpec; + prev = (direction == ssl_secret_write) ? ss->ssl3.cwSpec : ss->ssl3.crSpec; if (prev->epoch == PR_UINT16_MAX) { PORT_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED); return SECFailure; @@ -1417,7 +1417,7 @@ ssl3_SetupPendingCipherSpec(sslSocket *ss, CipherSpecDirection direction, spec->epoch = prev->epoch + 1; spec->nextSeqNum = 0; - if (IS_DTLS(ss) && direction == CipherSpecRead) { + if (IS_DTLS(ss) && direction == ssl_secret_read) { dtls_InitRecvdRecords(&spec->recvdRecords); } ssl_SetSpecVersions(ss, spec); @@ -1471,12 +1471,12 @@ ssl3_SetupBothPendingCipherSpecs(sslSocket *ss) ss->ssl3.hs.kea_def = &kea_defs[kea]; PORT_Assert(ss->ssl3.hs.kea_def->kea == kea); - rv = ssl3_SetupPendingCipherSpec(ss, CipherSpecRead, suiteDef, + rv = ssl3_SetupPendingCipherSpec(ss, ssl_secret_read, suiteDef, &ss->ssl3.prSpec); if (rv != SECSuccess) { goto loser; } - rv = ssl3_SetupPendingCipherSpec(ss, CipherSpecWrite, suiteDef, + rv = ssl3_SetupPendingCipherSpec(ss, ssl_secret_write, suiteDef, &ss->ssl3.pwSpec); if (rv != SECSuccess) { goto loser; @@ -1727,7 +1727,7 @@ ssl3_InitPendingContexts(sslSocket *ss, ssl3CipherSpec *spec) spec->cipher = (SSLCipher)PK11_CipherOp; encMechanism = ssl3_Alg2Mech(calg); - encMode = (spec->direction == CipherSpecWrite) ? CKA_ENCRYPT : CKA_DECRYPT; + encMode = (spec->direction == ssl_secret_write) ? CKA_ENCRYPT : CKA_DECRYPT; /* * build the context @@ -2215,7 +2215,7 @@ ssl_ProtectRecord(sslSocket *ss, ssl3CipherSpec *cwSpec, SSLContentType ct, unsigned int lenOffset; SECStatus rv; - PORT_Assert(cwSpec->direction == CipherSpecWrite); + PORT_Assert(cwSpec->direction == ssl_secret_write); PORT_Assert(SSL_BUFFER_LEN(wrBuf) == 0); PORT_Assert(cwSpec->cipherDef->max_records <= RECORD_SEQ_MAX); @@ -2314,8 +2314,8 @@ ssl_ProtectNextRecord(sslSocket *ss, ssl3CipherSpec *spec, SSLContentType ct, * Returns the number of bytes of plaintext that were successfully sent * plus the number of bytes of plaintext that were copied into the * output (write) buffer. - * Returns SECFailure on a hard IO error, memory error, or crypto error. - * Does NOT return SECWouldBlock. + * Returns -1 on an error. PR_WOULD_BLOCK_ERROR is set if the error is blocking + * and not terminal. * * Notes on the use of the private ssl flags: * (no private SSL flags) @@ -2360,13 +2360,26 @@ ssl3_SendRecord(sslSocket *ss, * error, so don't overwrite. */ PORT_SetError(SSL_ERROR_HANDSHAKE_FAILED); } - return SECFailure; + return -1; } /* check for Token Presence */ if (!ssl3_ClientAuthTokenPresent(ss->sec.ci.sid)) { PORT_SetError(SSL_ERROR_TOKEN_INSERTION_REMOVAL); - return SECFailure; + return -1; + } + + if (ss->recordWriteCallback) { + PRUint16 epoch; + ssl_GetSpecReadLock(ss); + epoch = ss->ssl3.cwSpec->epoch; + ssl_ReleaseSpecReadLock(ss); + rv = ss->recordWriteCallback(ss->fd, epoch, ct, pIn, nIn, + ss->recordWriteCallbackArg); + if (rv != SECSuccess) { + return -1; + } + return nIn; } if (cwSpec) { @@ -2470,7 +2483,7 @@ loser: #define SSL3_PENDING_HIGH_WATER 1024 /* Attempt to send the content of "in" in an SSL application_data record. - * Returns "len" or SECFailure, never SECWouldBlock, nor SECSuccess. + * Returns "len" or -1 on failure. */ int ssl3_SendApplicationData(sslSocket *ss, const unsigned char *in, @@ -2485,21 +2498,21 @@ ssl3_SendApplicationData(sslSocket *ss, const unsigned char *in, PORT_Assert(!(flags & ssl_SEND_FLAG_NO_RETRANSMIT)); if (len < 0 || !in) { PORT_SetError(PR_INVALID_ARGUMENT_ERROR); - return SECFailure; + return -1; } if (ss->pendingBuf.len > SSL3_PENDING_HIGH_WATER && !ssl_SocketIsBlocking(ss)) { PORT_Assert(!ssl_SocketIsBlocking(ss)); PORT_SetError(PR_WOULD_BLOCK_ERROR); - return SECFailure; + return -1; } if (ss->appDataBuffered && len) { PORT_Assert(in[0] == (unsigned char)(ss->appDataBuffered)); if (in[0] != (unsigned char)(ss->appDataBuffered)) { PORT_SetError(PR_INVALID_ARGUMENT_ERROR); - return SECFailure; + return -1; } in++; len--; @@ -2548,7 +2561,7 @@ ssl3_SendApplicationData(sslSocket *ss, const unsigned char *in, PORT_Assert(ss->lastWriteBlocked); break; } - return SECFailure; /* error code set by ssl3_SendRecord */ + return -1; /* error code set by ssl3_SendRecord */ } totalSent += sent; if (ss->pendingBuf.len) { @@ -2577,7 +2590,6 @@ ssl3_SendApplicationData(sslSocket *ss, const unsigned char *in, } /* Attempt to send buffered handshake messages. - * This function returns SECSuccess or SECFailure, never SECWouldBlock. * Always set sendBuf.len to 0, even when returning SECFailure. * * Depending on whether we are doing DTLS or not, this either calls @@ -2600,7 +2612,6 @@ ssl3_FlushHandshake(sslSocket *ss, PRInt32 flags) } /* Attempt to send the content of sendBuf buffer in an SSL handshake record. - * This function returns SECSuccess or SECFailure, never SECWouldBlock. * Always set sendBuf.len to 0, even when returning SECFailure. * * Called from ssl3_FlushHandshake @@ -7382,6 +7393,9 @@ ssl3_CompleteHandleCertificateRequest(sslSocket *ss, if (ss->getClientAuthData != NULL) { PORT_Assert((ss->ssl3.hs.preliminaryInfo & ssl_preinfo_all) == ssl_preinfo_all); + PORT_Assert(ss->ssl3.clientPrivateKey == NULL); + PORT_Assert(ss->ssl3.clientCertificate == NULL); + PORT_Assert(ss->ssl3.clientCertChain == NULL); /* XXX Should pass cert_types and algorithms in this call!! */ rv = (SECStatus)(*ss->getClientAuthData)(ss->getClientAuthDataArg, ss->fd, ca_list, @@ -7603,7 +7617,8 @@ ssl3_SendClientSecondRound(sslSocket *ss) " certificate authentication is still pending.", SSL_GETPID(), ss->fd)); ss->ssl3.hs.restartTarget = ssl3_SendClientSecondRound; - return SECWouldBlock; + PORT_SetError(PR_WOULD_BLOCK_ERROR); + return SECFailure; } ssl_GetXmitBufLock(ss); /*******************************/ @@ -10737,6 +10752,9 @@ ssl3_AuthCertificate(sslSocket *ss) } } + if (ss->sec.ci.sid->peerCert) { + CERT_DestroyCertificate(ss->sec.ci.sid->peerCert); + } ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert); if (!ss->sec.isServer) { @@ -10898,13 +10916,6 @@ ssl3_AuthCertificateComplete(sslSocket *ss, PRErrorCode error) } rv = target(ss); - /* Even if we blocked here, we have accomplished enough to claim - * success. Any remaining work will be taken care of by subsequent - * calls to SSL_ForceHandshake/PR_Send/PR_Read/etc. - */ - if (rv == SECWouldBlock) { - rv = SECSuccess; - } } else { SSL_TRC(3, ("%d: SSL3[%p]: certificate authentication won the race with" " peer's finished message", @@ -11445,7 +11456,8 @@ xmit_loser: } ss->ssl3.hs.restartTarget = ssl3_FinishHandshake; - return SECWouldBlock; + PORT_SetError(PR_WOULD_BLOCK_ERROR); + return SECFailure; } rv = ssl3_FinishHandshake(ss); @@ -11649,9 +11661,10 @@ ssl3_HandleHandshakeMessage(sslSocket *ss, PRUint8 *b, PRUint32 length, * authenticate the certificate in ssl3_HandleCertificateStatus. */ rv = ssl3_AuthCertificate(ss); /* sets ss->ssl3.hs.ws */ - PORT_Assert(rv != SECWouldBlock); if (rv != SECSuccess) { - return rv; + /* This can't block. */ + PORT_Assert(PORT_GetError() != PR_WOULD_BLOCK_ERROR); + return SECFailure; } } @@ -11809,28 +11822,17 @@ ssl3_HandlePostHelloHandshakeMessage(sslSocket *ss, PRUint8 *b, static SECStatus ssl3_HandleHandshake(sslSocket *ss, sslBuffer *origBuf) { - /* - * There may be a partial handshake message already in the handshake - * state. The incoming buffer may contain another portion, or a - * complete message or several messages followed by another portion. - * - * Each message is made contiguous before being passed to the actual - * message parser. - */ - sslBuffer *buf = &ss->ssl3.hs.msgState; /* do not lose the original buffer pointer */ + sslBuffer buf = *origBuf; /* Work from a copy. */ SECStatus rv; PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss)); PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); - if (buf->buf == NULL) { - *buf = *origBuf; - } - while (buf->len > 0) { + while (buf.len > 0) { if (ss->ssl3.hs.header_bytes < 4) { PRUint8 t; - t = *(buf->buf++); - buf->len--; + t = *(buf.buf++); + buf.len--; if (ss->ssl3.hs.header_bytes++ == 0) ss->ssl3.hs.msg_type = (SSLHandshakeType)t; else @@ -11847,7 +11849,7 @@ ssl3_HandleHandshake(sslSocket *ss, sslBuffer *origBuf) #undef MAX_HANDSHAKE_MSG_LEN /* If msg_len is zero, be sure we fall through, - ** even if buf->len is zero. + ** even if buf.len is zero. */ if (ss->ssl3.hs.msg_len > 0) continue; @@ -11858,22 +11860,15 @@ ssl3_HandleHandshake(sslSocket *ss, sslBuffer *origBuf) * data available for this message. If it can be done right out * of the original buffer, then use it from there. */ - if (ss->ssl3.hs.msg_body.len == 0 && buf->len >= ss->ssl3.hs.msg_len) { + if (ss->ssl3.hs.msg_body.len == 0 && buf.len >= ss->ssl3.hs.msg_len) { /* handle it from input buffer */ - rv = ssl3_HandleHandshakeMessage(ss, buf->buf, ss->ssl3.hs.msg_len, - buf->len == ss->ssl3.hs.msg_len); - if (rv == SECFailure) { - /* This test wants to fall through on either - * SECSuccess or SECWouldBlock. - * ssl3_HandleHandshakeMessage MUST set the error code. - */ - return rv; - } - buf->buf += ss->ssl3.hs.msg_len; - buf->len -= ss->ssl3.hs.msg_len; + rv = ssl3_HandleHandshakeMessage(ss, buf.buf, ss->ssl3.hs.msg_len, + buf.len == ss->ssl3.hs.msg_len); + buf.buf += ss->ssl3.hs.msg_len; + buf.len -= ss->ssl3.hs.msg_len; ss->ssl3.hs.msg_len = 0; ss->ssl3.hs.header_bytes = 0; - if (rv != SECSuccess) { /* return if SECWouldBlock. */ + if (rv != SECSuccess) { return rv; } } else { @@ -11881,7 +11876,7 @@ ssl3_HandleHandshake(sslSocket *ss, sslBuffer *origBuf) unsigned int bytes; PORT_Assert(ss->ssl3.hs.msg_body.len < ss->ssl3.hs.msg_len); - bytes = PR_MIN(buf->len, ss->ssl3.hs.msg_len - ss->ssl3.hs.msg_body.len); + bytes = PR_MIN(buf.len, ss->ssl3.hs.msg_len - ss->ssl3.hs.msg_body.len); /* Grow the buffer if needed */ rv = sslBuffer_Grow(&ss->ssl3.hs.msg_body, ss->ssl3.hs.msg_len); @@ -11891,10 +11886,10 @@ ssl3_HandleHandshake(sslSocket *ss, sslBuffer *origBuf) } PORT_Memcpy(ss->ssl3.hs.msg_body.buf + ss->ssl3.hs.msg_body.len, - buf->buf, bytes); + buf.buf, bytes); ss->ssl3.hs.msg_body.len += bytes; - buf->buf += bytes; - buf->len -= bytes; + buf.buf += bytes; + buf.len -= bytes; PORT_Assert(ss->ssl3.hs.msg_body.len <= ss->ssl3.hs.msg_len); @@ -11902,29 +11897,21 @@ ssl3_HandleHandshake(sslSocket *ss, sslBuffer *origBuf) if (ss->ssl3.hs.msg_body.len == ss->ssl3.hs.msg_len) { rv = ssl3_HandleHandshakeMessage( ss, ss->ssl3.hs.msg_body.buf, ss->ssl3.hs.msg_len, - buf->len == 0); - if (rv == SECFailure) { - /* This test wants to fall through on either - * SECSuccess or SECWouldBlock. - * ssl3_HandleHandshakeMessage MUST set error code. - */ - return rv; - } + buf.len == 0); ss->ssl3.hs.msg_body.len = 0; ss->ssl3.hs.msg_len = 0; ss->ssl3.hs.header_bytes = 0; - if (rv != SECSuccess) { /* return if SECWouldBlock. */ + if (rv != SECSuccess) { return rv; } } else { - PORT_Assert(buf->len == 0); + PORT_Assert(buf.len == 0); break; } } } /* end loop */ origBuf->len = 0; /* So ssl3_GatherAppDataRecord will keep looping. */ - buf->buf = NULL; /* not a leak. */ return SECSuccess; } @@ -12183,7 +12170,7 @@ ssl3_UnprotectRecord(sslSocket *ss, unsigned int hashBytes = MAX_MAC_LENGTH + 1; SECStatus rv; - PORT_Assert(spec->direction == CipherSpecRead); + PORT_Assert(spec->direction == ssl_secret_read); good = ~0U; minLength = spec->macDef->mac_size; @@ -12372,7 +12359,7 @@ ssl3_HandleNonApplicationData(sslSocket *ss, SSLContentType rType, ssl_GetSSL3HandshakeLock(ss); /* All the functions called in this switch MUST set error code if - ** they return SECFailure or SECWouldBlock. + ** they return SECFailure. */ switch (rType) { case ssl_ct_change_cipher_spec: @@ -12429,7 +12416,7 @@ ssl3_GetCipherSpec(sslSocket *ss, SSL3Ciphertext *cText) } if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3) { /* Try to find the cipher spec. */ - newSpec = ssl_FindCipherSpecByEpoch(ss, CipherSpecRead, + newSpec = ssl_FindCipherSpecByEpoch(ss, ssl_secret_read, epoch); if (newSpec != NULL) { return newSpec; @@ -12694,8 +12681,8 @@ ssl3_InitState(sslSocket *ss) ssl_GetSpecWriteLock(ss); PR_INIT_CLIST(&ss->ssl3.hs.cipherSpecs); - rv = ssl_SetupNullCipherSpec(ss, CipherSpecRead); - rv |= ssl_SetupNullCipherSpec(ss, CipherSpecWrite); + rv = ssl_SetupNullCipherSpec(ss, ssl_secret_read); + rv |= ssl_SetupNullCipherSpec(ss, ssl_secret_write); ss->ssl3.pwSpec = ss->ssl3.prSpec = NULL; ssl_ReleaseSpecWriteLock(ss); if (rv != SECSuccess) { diff --git a/security/nss/lib/ssl/ssl3ext.c b/security/nss/lib/ssl/ssl3ext.c index 60b5889e7286..e9fc096b7399 100644 --- a/security/nss/lib/ssl/ssl3ext.c +++ b/security/nss/lib/ssl/ssl3ext.c @@ -51,6 +51,7 @@ static const ssl3ExtensionHandler clientHelloHandlers[] = { { ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ServerHandlePskModesXtn }, { ssl_tls13_cookie_xtn, &tls13_ServerHandleCookieXtn }, { ssl_tls13_encrypted_sni_xtn, &tls13_ServerHandleEsniXtn }, + { ssl_tls13_post_handshake_auth_xtn, &tls13_ServerHandlePostHandshakeAuthXtn }, { ssl_record_size_limit_xtn, &ssl_HandleRecordSizeLimitXtn }, { 0, NULL } }; @@ -138,6 +139,7 @@ static const sslExtensionBuilder clientHelloSendersTLS[] = { ssl_tls13_cookie_xtn, &tls13_ClientSendHrrCookieXtn }, { ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ClientSendPskModesXtn }, { ssl_tls13_encrypted_sni_xtn, &tls13_ClientSendEsniXtn }, + { ssl_tls13_post_handshake_auth_xtn, &tls13_ClientSendPostHandshakeAuthXtn }, { ssl_record_size_limit_xtn, &ssl_SendRecordSizeLimitXtn }, /* The pre_shared_key extension MUST be last. */ { ssl_tls13_pre_shared_key_xtn, &tls13_ClientSendPreSharedKeyXtn }, diff --git a/security/nss/lib/ssl/ssl3gthr.c b/security/nss/lib/ssl/ssl3gthr.c index 64a1878f7639..5f8322b7f951 100644 --- a/security/nss/lib/ssl/ssl3gthr.c +++ b/security/nss/lib/ssl/ssl3gthr.c @@ -389,7 +389,6 @@ dtls_GatherData(sslSocket *ss, sslGather *gs, int flags) * application data is available. * Returns 0 if ssl3_GatherData hits EOF. * Returns -1 on read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error. - * Returns -2 on SECWouldBlock return from ssl3_HandleRecord. * * Called from ssl_GatherRecord1stHandshake in sslcon.c, * and from SSL_ForceHandshake in sslsecur.c @@ -422,13 +421,19 @@ ssl3_GatherCompleteHandshake(sslSocket *ss, int flags) PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss)); do { - PRBool handleRecordNow = PR_FALSE; PRBool processingEarlyData; ssl_GetSSL3HandshakeLock(ss); processingEarlyData = ss->ssl3.hs.zeroRttState == ssl_0rtt_accepted; + /* If we have a detached record layer, don't ever gather. */ + if (ss->recordWriteCallback) { + ssl_ReleaseSSL3HandshakeLock(ss); + PORT_SetError(PR_WOULD_BLOCK_ERROR); + return (int)SECFailure; + } + /* Without this, we may end up wrongly reporting * SSL_ERROR_RX_UNEXPECTED_* errors if we receive any records from the * peer while we are waiting to be restarted. @@ -439,93 +444,68 @@ ssl3_GatherCompleteHandshake(sslSocket *ss, int flags) return (int)SECFailure; } - /* Treat an empty msgState like a NULL msgState. (Most of the time - * when ssl3_HandleHandshake returns SECWouldBlock, it leaves - * behind a non-NULL but zero-length msgState). - * Test: async_cert_restart_server_sends_hello_request_first_in_separate_record - */ - if (ss->ssl3.hs.msgState.buf) { - if (ss->ssl3.hs.msgState.len == 0) { - ss->ssl3.hs.msgState.buf = NULL; - } else { - handleRecordNow = PR_TRUE; + ssl_ReleaseSSL3HandshakeLock(ss); + + /* State for SSLv2 client hello support. */ + ssl2Gather ssl2gs = { PR_FALSE, 0 }; + ssl2Gather *ssl2gs_ptr = NULL; + + /* If we're a server and waiting for a client hello, accept v2. */ + if (ss->sec.isServer && ss->opt.enableV2CompatibleHello && + ss->ssl3.hs.ws == wait_client_hello) { + ssl2gs_ptr = &ssl2gs; + } + + /* bring in the next sslv3 record. */ + if (ss->recvdCloseNotify) { + /* RFC 5246 Section 7.2.1: + * Any data received after a closure alert is ignored. + */ + return 0; + } + + if (!IS_DTLS(ss)) { + /* If we're a server waiting for a ClientHello then pass + * ssl2gs to support SSLv2 ClientHello messages. */ + rv = ssl3_GatherData(ss, &ss->gs, flags, ssl2gs_ptr); + } else { + rv = dtls_GatherData(ss, &ss->gs, flags); + + /* If we got a would block error, that means that no data was + * available, so we check the timer to see if it's time to + * retransmit */ + if (rv == SECFailure && + (PORT_GetError() == PR_WOULD_BLOCK_ERROR)) { + dtls_CheckTimer(ss); + /* Restore the error in case something succeeded */ + PORT_SetError(PR_WOULD_BLOCK_ERROR); } } - ssl_ReleaseSSL3HandshakeLock(ss); + if (rv <= 0) { + return rv; + } - if (handleRecordNow) { - /* ssl3_HandleHandshake previously returned SECWouldBlock and the - * as-yet-unprocessed plaintext of that previous handshake record. - * We need to process it now before we overwrite it with the next - * handshake record. - */ - SSL_DBG(("%d: SSL3[%d]: resuming handshake", - SSL_GETPID(), ss->fd)); - PORT_Assert(!IS_DTLS(ss)); - rv = ssl3_HandleNonApplicationData(ss, ssl_ct_handshake, - 0, 0, &ss->gs.buf); - } else { - /* State for SSLv2 client hello support. */ - ssl2Gather ssl2gs = { PR_FALSE, 0 }; - ssl2Gather *ssl2gs_ptr = NULL; - - if (ss->sec.isServer && ss->opt.enableV2CompatibleHello && - ss->ssl3.hs.ws == wait_client_hello) { - ssl2gs_ptr = &ssl2gs; - } - - /* bring in the next sslv3 record. */ - if (ss->recvdCloseNotify) { - /* RFC 5246 Section 7.2.1: - * Any data received after a closure alert is ignored. - */ - return 0; - } - - if (!IS_DTLS(ss)) { - /* Passing a non-NULL ssl2gs here enables detection of - * SSLv2-compatible ClientHello messages. */ - rv = ssl3_GatherData(ss, &ss->gs, flags, ssl2gs_ptr); - } else { - rv = dtls_GatherData(ss, &ss->gs, flags); - - /* If we got a would block error, that means that no data was - * available, so we check the timer to see if it's time to - * retransmit */ - if (rv == SECFailure && - (PORT_GetError() == PR_WOULD_BLOCK_ERROR)) { - dtls_CheckTimer(ss); - /* Restore the error in case something succeeded */ - PORT_SetError(PR_WOULD_BLOCK_ERROR); - } - } - - if (rv <= 0) { + if (ssl2gs.isV2) { + rv = ssl3_HandleV2ClientHello(ss, ss->gs.inbuf.buf, + ss->gs.inbuf.len, + ssl2gs.padding); + if (rv < 0) { return rv; } - - if (ssl2gs.isV2) { - rv = ssl3_HandleV2ClientHello(ss, ss->gs.inbuf.buf, - ss->gs.inbuf.len, - ssl2gs.padding); - if (rv < 0) { - return rv; - } - } else { - /* decipher it, and handle it if it's a handshake. - * If it's application data, ss->gs.buf will not be empty upon return. - * If it's a change cipher spec, alert, or handshake message, - * ss->gs.buf.len will be 0 when ssl3_HandleRecord returns SECSuccess. - * - * cText only needs to be valid for this next function call, so - * it can borrow gs.hdr. - */ - cText.hdr = ss->gs.hdr; - cText.hdrLen = ss->gs.hdrLen; - cText.buf = &ss->gs.inbuf; - rv = ssl3_HandleRecord(ss, &cText); - } + } else { + /* decipher it, and handle it if it's a handshake. + * If it's application data, ss->gs.buf will not be empty upon return. + * If it's a change cipher spec, alert, or handshake message, + * ss->gs.buf.len will be 0 when ssl3_HandleRecord returns SECSuccess. + * + * cText only needs to be valid for this next function call, so + * it can borrow gs.hdr. + */ + cText.hdr = ss->gs.hdr; + cText.hdrLen = ss->gs.hdrLen; + cText.buf = &ss->gs.inbuf; + rv = ssl3_HandleRecord(ss, &cText); } if (rv < 0) { return ss->recvdCloseNotify ? 0 : rv; @@ -575,7 +555,7 @@ ssl3_GatherCompleteHandshake(sslSocket *ss, int flags) * delivered to the application before the handshake completes. */ ssl_ReleaseSSL3HandshakeLock(ss); PORT_SetError(PR_WOULD_BLOCK_ERROR); - return SECWouldBlock; + return -1; } ssl_ReleaseSSL3HandshakeLock(ss); } while (keepGoing); @@ -596,7 +576,6 @@ ssl3_GatherCompleteHandshake(sslSocket *ss, int flags) * Returns 1 when application data is available. * Returns 0 if ssl3_GatherData hits EOF. * Returns -1 on read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error. - * Returns -2 on SECWouldBlock return from ssl3_HandleRecord. * * Called from DoRecv in sslsecur.c * Caller must hold the recv buf lock. @@ -616,3 +595,108 @@ ssl3_GatherAppDataRecord(sslSocket *ss, int flags) return rv; } + +SECStatus +SSLExp_RecordLayerData(PRFileDesc *fd, PRUint16 epoch, + SSLContentType contentType, + const PRUint8 *data, unsigned int len) +{ + SECStatus rv; + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + return SECFailure; + } + if (IS_DTLS(ss) || data == NULL || len == 0) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* Run any handshake function. If SSL_RecordLayerData is the only way that + * the handshake is driven, then this is necessary to ensure that + * ssl_BeginClientHandshake or ssl_BeginServerHandshake is called. Note that + * the other function that might be set to ss->handshake, + * ssl3_GatherCompleteHandshake, does nothing when this function is used. */ + ssl_Get1stHandshakeLock(ss); + rv = ssl_Do1stHandshake(ss); + if (rv != SECSuccess && PORT_GetError() != PR_WOULD_BLOCK_ERROR) { + goto early_loser; /* Rely on the existing code. */ + } + + /* Don't allow application data before handshake completion. */ + if (contentType == ssl_ct_application_data && !ss->firstHsDone) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + goto early_loser; + } + + /* Then we can validate the epoch. */ + PRErrorCode epochError; + ssl_GetSpecReadLock(ss); + if (epoch < ss->ssl3.crSpec->epoch) { + epochError = SEC_ERROR_INVALID_ARGS; /* Too c/old. */ + } else if (epoch > ss->ssl3.crSpec->epoch) { + epochError = PR_WOULD_BLOCK_ERROR; /* Too warm/new. */ + } else { + epochError = 0; /* Just right. */ + } + ssl_ReleaseSpecReadLock(ss); + if (epochError) { + PORT_SetError(epochError); + goto early_loser; + } + + /* If the handshake is still running, we need to run that. */ + ssl_Get1stHandshakeLock(ss); + rv = ssl_Do1stHandshake(ss); + if (rv != SECSuccess && PORT_GetError() != PR_WOULD_BLOCK_ERROR) { + ssl_Release1stHandshakeLock(ss); + return SECFailure; + } + + /* Finally, save the data... */ + ssl_GetRecvBufLock(ss); + rv = sslBuffer_Append(&ss->gs.buf, data, len); + if (rv != SECSuccess) { + goto loser; + } + + /* ...and process it. Just saving application data is enough for it to be + * available to PR_Read(). */ + if (contentType != ssl_ct_application_data) { + rv = ssl3_HandleNonApplicationData(ss, contentType, 0, 0, &ss->gs.buf); + if (rv != SECSuccess) { + goto loser; + } + } + + ssl_ReleaseRecvBufLock(ss); + ssl_Release1stHandshakeLock(ss); + return SECSuccess; + +loser: + /* Make sure that any data is not used again. */ + ss->gs.buf.len = 0; + ssl_ReleaseRecvBufLock(ss); +early_loser: + ssl_Release1stHandshakeLock(ss); + return SECFailure; +} + +SECStatus +SSLExp_GetCurrentEpoch(PRFileDesc *fd, PRUint16 *readEpoch, + PRUint16 *writeEpoch) +{ + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + return SECFailure; + } + + ssl_GetSpecReadLock(ss); + if (readEpoch) { + *readEpoch = ss->ssl3.crSpec->epoch; + } + if (writeEpoch) { + *writeEpoch = ss->ssl3.cwSpec->epoch; + } + ssl_ReleaseSpecReadLock(ss); + return SECSuccess; +} diff --git a/security/nss/lib/ssl/sslcon.c b/security/nss/lib/ssl/sslcon.c index bc63e1537129..603da3e1586a 100644 --- a/security/nss/lib/ssl/sslcon.c +++ b/security/nss/lib/ssl/sslcon.c @@ -44,17 +44,13 @@ const char *ssl_version = "SECURITY_VERSION:" * This function acquires and releases the RecvBufLock. * * returns SECSuccess for success. - * returns SECWouldBlock when that value is returned by - * ssl3_GatherCompleteHandshake(). - * returns SECFailure on all other errors. + * returns SECFailure on error, setting PR_WOULD_BLOCK_ERROR if only blocked. * * The gather functions called by ssl_GatherRecord1stHandshake are expected * to return values interpreted as follows: * 1 : the function completed without error. * 0 : the function read EOF. * -1 : read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error. - * -2 : the function wants ssl_GatherRecord1stHandshake to be called again - * immediately, by ssl_Do1stHandshake. * * This code is similar to, and easily confused with, DoRecv() in sslsecur.c * @@ -82,16 +78,14 @@ ssl_GatherRecord1stHandshake(sslSocket *ss) ssl_ReleaseRecvBufLock(ss); if (rv <= 0) { - if (rv == SECWouldBlock) { - /* Progress is blocked waiting for callback completion. */ - SSL_TRC(10, ("%d: SSL[%d]: handshake blocked (need %d)", - SSL_GETPID(), ss->fd, ss->gs.remainder)); - return SECWouldBlock; - } if (rv == 0) { /* EOF. Loser */ PORT_SetError(PR_END_OF_FILE_ERROR); } + if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) { + SSL_TRC(10, ("%d: SSL[%d]: handshake blocked (need %d)", + SSL_GETPID(), ss->fd, ss->gs.remainder)); + } return SECFailure; /* rv is < 0 here. */ } diff --git a/security/nss/lib/ssl/ssldef.c b/security/nss/lib/ssl/ssldef.c index be5bcb26945d..3ed1979507ab 100644 --- a/security/nss/lib/ssl/ssldef.c +++ b/security/nss/lib/ssl/ssldef.c @@ -84,7 +84,7 @@ ssl_DefRecv(sslSocket *ss, unsigned char *buf, int len, int flags) * For blocking sockets, always returns len or SECFailure, no short writes. * For non-blocking sockets: * Returns positive count if any data was written, else returns SECFailure. - * Short writes may occur. Does not return SECWouldBlock. + * Short writes may occur. */ int ssl_DefSend(sslSocket *ss, const unsigned char *buf, int len, int flags) diff --git a/security/nss/lib/ssl/sslerr.h b/security/nss/lib/ssl/sslerr.h index a4aa2765769c..b8f4e30eaf2f 100644 --- a/security/nss/lib/ssl/sslerr.h +++ b/security/nss/lib/ssl/sslerr.h @@ -268,6 +268,7 @@ typedef enum { SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION = (SSL_ERROR_BASE + 177), SSL_ERROR_MISSING_ESNI_EXTENSION = (SSL_ERROR_BASE + 178), SSL_ERROR_RX_UNEXPECTED_RECORD_TYPE = (SSL_ERROR_BASE + 179), + SSL_ERROR_MISSING_POST_HANDSHAKE_AUTH_EXTENSION = (SSL_ERROR_BASE + 180), SSL_ERROR_END_OF_LIST /* let the c compiler determine the value of this. */ } SSLErrorCodes; #endif /* NO_SECURITY_ERROR_ENUM */ diff --git a/security/nss/lib/ssl/sslexp.h b/security/nss/lib/ssl/sslexp.h index f450e528dc36..8f4720cdc4ad 100644 --- a/security/nss/lib/ssl/sslexp.h +++ b/security/nss/lib/ssl/sslexp.h @@ -350,6 +350,27 @@ typedef SSLHelloRetryRequestAction(PR_CALLBACK *SSLHelloRetryRequestCallback)( (PRFileDesc * _fd, PRBool _requestUpdate), \ (fd, requestUpdate)) +/* This function allows a server application to trigger + * re-authentication (TLS 1.3 only) after handshake. + * + * This function will cause a CertificateRequest message to be sent by + * a server. This can be called once at a time, and is not allowed + * until an answer is received. + * + * The AuthCertificateCallback is called when the answer is received. + * If the answer is accepted by the server, the value returned by + * SSL_PeerCertificate() is replaced. If you need to remember all the + * certificates, you will need to call SSL_PeerCertificate() and save + * what you get before calling this. + * + * If the AuthCertificateCallback returns SECFailure, the connection + * is aborted. + */ +#define SSL_SendCertificateRequest(fd) \ + SSL_EXPERIMENTAL_API("SSL_SendCertificateRequest", \ + (PRFileDesc * _fd), \ + (fd)) + /* * Session cache API. */ @@ -511,6 +532,100 @@ typedef SECStatus(PR_CALLBACK *SSLResumptionTokenCallback)( group, pubKey, pad, notBefore, notAfter, \ out, outlen, maxlen)) +/* SSL_SetSecretCallback installs a callback that TLS calls when it installs new + * traffic secrets. + * + * SSLSecretCallback is called with the current epoch and the corresponding + * secret; this matches the epoch used in DTLS 1.3, even if the socket is + * operating in stream mode: + * + * - client_early_traffic_secret corresponds to epoch 1 + * - {client|server}_handshake_traffic_secret is epoch 2 + * - {client|server}_application_traffic_secret_{N} is epoch 3+N + * + * The callback is invoked separately for read secrets (client secrets on the + * server; server secrets on the client), and write secrets. + * + * This callback is only called if (D)TLS 1.3 is negotiated. + */ +typedef void(PR_CALLBACK *SSLSecretCallback)( + PRFileDesc *fd, PRUint16 epoch, SSLSecretDirection dir, PK11SymKey *secret, + void *arg); + +#define SSL_SecretCallback(fd, cb, arg) \ + SSL_EXPERIMENTAL_API("SSL_SecretCallback", \ + (PRFileDesc * _fd, SSLSecretCallback _cb, void *_arg), \ + (fd, cb, arg)) + +/* SSL_RecordLayerWriteCallback() is used to replace the TLS record layer. This + * function installs a callback that TLS calls when it would otherwise encrypt + * and write a record to the underlying NSPR IO layer. The application is + * responsible for ensuring that these records are encrypted and written. + * + * Calling this API also disables reads from the underlying NSPR layer. The + * application is expected to push data when it is available using + * SSL_RecordLayerData(). + * + * When data would be written, the provided SSLRecordWriteCallback with the + * epoch, TLS content type, and the data. The data provided to the callback is + * not split into record-sized writes. If the callback returns SECFailure, the + * write will be considered to have failed; in particular, PR_WOULD_BLOCK_ERROR + * is not handled specially. + * + * If TLS 1.3 is in use, the epoch indicates the expected level of protection + * that the record would receive, this matches that used in DTLS 1.3: + * + * - epoch 0 corresponds to no record protection + * - epoch 1 corresponds to 0-RTT + * - epoch 2 corresponds to TLS handshake + * - epoch 3 and higher are application data + * + * Prior versions of TLS use epoch 1 and higher for application data. + * + * This API is not supported for DTLS. + */ +typedef SECStatus(PR_CALLBACK *SSLRecordWriteCallback)( + PRFileDesc *fd, PRUint16 epoch, SSLContentType contentType, + const PRUint8 *data, unsigned int len, void *arg); + +#define SSL_RecordLayerWriteCallback(fd, writeCb, arg) \ + SSL_EXPERIMENTAL_API("SSL_RecordLayerWriteCallback", \ + (PRFileDesc * _fd, SSLRecordWriteCallback _wCb, \ + void *_arg), \ + (fd, writeCb, arg)) + +/* SSL_RecordLayerData() is used to provide new data to TLS. The application + * indicates the epoch (see the description of SSL_RecordLayerWriteCallback()), + * content type, and the data that was received. The application is responsible + * for removing any encryption or other protection before passing data to this + * function. + * + * This returns SECSuccess if the data was successfully processed. If this + * function is used to drive the handshake and the caller needs to know when the + * handshake is complete, a call to SSL_ForceHandshake will return SECSuccess + * when the handshake is complete. + * + * This API is not supported for DTLS sockets. + */ +#define SSL_RecordLayerData(fd, epoch, ct, data, len) \ + SSL_EXPERIMENTAL_API("SSL_RecordLayerData", \ + (PRFileDesc * _fd, PRUint16 _epoch, \ + SSLContentType _contentType, \ + const PRUint8 *_data, unsigned int _len), \ + (fd, epoch, ct, data, len)) + +/* + * SSL_GetCurrentEpoch() returns the read and write epochs that the socket is + * currently using. NULL values for readEpoch or writeEpoch are ignored. + * + * See SSL_RecordLayerWriteCallback() for details on epochs. + */ +#define SSL_GetCurrentEpoch(fd, readEpoch, writeEpoch) \ + SSL_EXPERIMENTAL_API("SSL_GetCurrentEpoch", \ + (PRFileDesc * _fd, PRUint16 * _readEpoch, \ + PRUint16 * _writeEpoch), \ + (fd, readEpoch, writeEpoch)) + /* Deprecated experimental APIs */ #define SSL_UseAltServerHelloType(fd, enable) SSL_DEPRECATED_EXPERIMENTAL_API diff --git a/security/nss/lib/ssl/sslimpl.h b/security/nss/lib/ssl/sslimpl.h index 35240d2fba90..d1bc69478bed 100644 --- a/security/nss/lib/ssl/sslimpl.h +++ b/security/nss/lib/ssl/sslimpl.h @@ -272,6 +272,7 @@ typedef struct sslOptionsStr { unsigned int enableDtlsShortHeader : 1; unsigned int enableHelloDowngradeCheck : 1; unsigned int enableV2CompatibleHello : 1; + unsigned int enablePostHandshakeAuth : 1; } sslOptions; typedef enum { sslHandshakingUndetermined = 0, @@ -622,8 +623,6 @@ typedef struct SSL3HandshakeStateStr { unsigned long msg_len; PRBool isResuming; /* we are resuming (not used in TLS 1.3) */ PRBool sendingSCSV; /* instead of empty RI */ - sslBuffer msgState; /* current state for handshake messages*/ - /* protected by recvBufLock */ /* The session ticket received in a NewSessionTicket message is temporarily * stored in newSessionTicket until the handshake is finished; then it is @@ -744,10 +743,10 @@ struct ssl3StateStr { * update is initiated locally. */ PRBool peerRequestedKeyUpdate; - /* Internal callback for when we do a cipher suite change. Used for - * debugging in TLS 1.3. This can only be set by non-public functions. */ - sslCipherSpecChangedFunc changedCipherSpecFunc; - void *changedCipherSpecArg; + /* This is true after the server requests client certificate; + * false after the client certificate is received. Used by the + * server. */ + PRBool clientCertRequested; CERTCertificate *clientCertificate; /* used by client */ SECKEYPrivateKey *clientPrivateKey; /* used by client */ @@ -994,6 +993,10 @@ struct sslSocketStr { PRCList extensionHooks; SSLResumptionTokenCallback resumptionTokenCallback; void *resumptionTokenContext; + SSLSecretCallback secretCallback; + void *secretCallbackArg; + SSLRecordWriteCallback recordWriteCallback; + void *recordWriteCallbackArg; PRIntervalTime rTimeout; /* timeout for NSPR I/O */ PRIntervalTime wTimeout; /* timeout for NSPR I/O */ @@ -1174,7 +1177,7 @@ extern SECStatus ssl_SaveWriteData(sslSocket *ss, const void *p, unsigned int l); extern SECStatus ssl_BeginClientHandshake(sslSocket *ss); extern SECStatus ssl_BeginServerHandshake(sslSocket *ss); -extern int ssl_Do1stHandshake(sslSocket *ss); +extern SECStatus ssl_Do1stHandshake(sslSocket *ss); extern SECStatus ssl3_InitPendingCipherSpecs(sslSocket *ss, PK11SymKey *secret, PRBool derive); @@ -1742,6 +1745,17 @@ SECStatus SSLExp_GetResumptionTokenInfo(const PRUint8 *tokenData, unsigned int t SECStatus SSLExp_DestroyResumptionTokenInfo(SSLResumptionTokenInfo *token); +SECStatus SSLExp_SecretCallback(PRFileDesc *fd, SSLSecretCallback cb, + void *arg); +SECStatus SSLExp_RecordLayerWriteCallback(PRFileDesc *fd, + SSLRecordWriteCallback write, + void *arg); +SECStatus SSLExp_RecordLayerData(PRFileDesc *fd, PRUint16 epoch, + SSLContentType contentType, + const PRUint8 *data, unsigned int len); +SECStatus SSLExp_GetCurrentEpoch(PRFileDesc *fd, PRUint16 *readEpoch, + PRUint16 *writeEpoch); + #define SSLResumptionTokenVersion 2 SEC_END_PROTOS diff --git a/security/nss/lib/ssl/sslinfo.c b/security/nss/lib/ssl/sslinfo.c index 4e58c5ae7c3b..9eb22f081133 100644 --- a/security/nss/lib/ssl/sslinfo.c +++ b/security/nss/lib/ssl/sslinfo.c @@ -150,6 +150,7 @@ SSL_GetPreliminaryChannelInfo(PRFileDesc *fd, } else { inf.maxEarlyDataSize = 0; } + inf.zeroRttCipherSuite = ss->ssl3.hs.zeroRttSuite; memcpy(info, &inf, inf.length); return SECSuccess; @@ -234,89 +235,89 @@ SSL_GetPreliminaryChannelInfo(PRFileDesc *fd, static const SSLCipherSuiteInfo suiteInfo[] = { /* <------ Cipher suite --------------------> */ - { 0, CS_(TLS_AES_128_GCM_SHA256), S_ANY, K_ANY, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_ANY }, - { 0, CS_(TLS_CHACHA20_POLY1305_SHA256), S_ANY, K_ANY, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_ANY }, - { 0, CS_(TLS_AES_256_GCM_SHA384), S_ANY, K_ANY, C_AESGCM, B_256, M_AEAD_128, F_NFIPS_STD, A_ANY }, + { 0, CS_(TLS_AES_128_GCM_SHA256), S_ANY, K_ANY, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_ANY, ssl_hash_sha256 }, + { 0, CS_(TLS_CHACHA20_POLY1305_SHA256), S_ANY, K_ANY, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_ANY, ssl_hash_sha256 }, + { 0, CS_(TLS_AES_256_GCM_SHA384), S_ANY, K_ANY, C_AESGCM, B_256, M_AEAD_128, F_NFIPS_STD, A_ANY, ssl_hash_sha384 }, - { 0, CS(RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_RSA, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_RSAD }, - { 0, CS(DHE_RSA_WITH_CHACHA20_POLY1305_SHA256), S_RSA, K_DHE, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_RSAS }, + { 0, CS(RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_RSA, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_RSAD, ssl_hash_sha256 }, + { 0, CS(DHE_RSA_WITH_CHACHA20_POLY1305_SHA256), S_RSA, K_DHE, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_RSAS, ssl_hash_sha256 }, - { 0, CS(DHE_RSA_WITH_CAMELLIA_256_CBC_SHA), S_RSA, K_DHE, C_CAMELLIA, B_256, M_SHA, F_NFIPS_STD, A_RSAS }, - { 0, CS(DHE_DSS_WITH_CAMELLIA_256_CBC_SHA), S_DSA, K_DHE, C_CAMELLIA, B_256, M_SHA, F_NFIPS_STD, A_DSA }, - { 0, CS(DHE_RSA_WITH_AES_256_CBC_SHA256), S_RSA, K_DHE, C_AES, B_256, M_SHA256, F_FIPS_STD, A_RSAS }, - { 0, CS(DHE_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_DHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_RSAS }, - { 0, CS(DHE_DSS_WITH_AES_256_CBC_SHA), S_DSA, K_DHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_DSA }, - { 0, CS(DHE_DSS_WITH_AES_256_CBC_SHA256), S_DSA, K_DHE, C_AES, B_256, M_SHA256, F_FIPS_STD, A_DSA }, - { 0, CS(RSA_WITH_CAMELLIA_256_CBC_SHA), S_RSA, K_RSA, C_CAMELLIA, B_256, M_SHA, F_NFIPS_STD, A_RSAD }, - { 0, CS(RSA_WITH_AES_256_CBC_SHA256), S_RSA, K_RSA, C_AES, B_256, M_SHA256, F_FIPS_STD, A_RSAD }, - { 0, CS(RSA_WITH_AES_256_CBC_SHA), S_RSA, K_RSA, C_AES, B_256, M_SHA, F_FIPS_STD, A_RSAD }, + { 0, CS(DHE_RSA_WITH_CAMELLIA_256_CBC_SHA), S_RSA, K_DHE, C_CAMELLIA, B_256, M_SHA, F_NFIPS_STD, A_RSAS, ssl_hash_none }, + { 0, CS(DHE_DSS_WITH_CAMELLIA_256_CBC_SHA), S_DSA, K_DHE, C_CAMELLIA, B_256, M_SHA, F_NFIPS_STD, A_DSA, ssl_hash_none }, + { 0, CS(DHE_RSA_WITH_AES_256_CBC_SHA256), S_RSA, K_DHE, C_AES, B_256, M_SHA256, F_FIPS_STD, A_RSAS, ssl_hash_sha256 }, + { 0, CS(DHE_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_DHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_RSAS, ssl_hash_none }, + { 0, CS(DHE_DSS_WITH_AES_256_CBC_SHA), S_DSA, K_DHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_DSA, ssl_hash_none }, + { 0, CS(DHE_DSS_WITH_AES_256_CBC_SHA256), S_DSA, K_DHE, C_AES, B_256, M_SHA256, F_FIPS_STD, A_DSA, ssl_hash_sha256 }, + { 0, CS(RSA_WITH_CAMELLIA_256_CBC_SHA), S_RSA, K_RSA, C_CAMELLIA, B_256, M_SHA, F_NFIPS_STD, A_RSAD, ssl_hash_none }, + { 0, CS(RSA_WITH_AES_256_CBC_SHA256), S_RSA, K_RSA, C_AES, B_256, M_SHA256, F_FIPS_STD, A_RSAD, ssl_hash_sha256 }, + { 0, CS(RSA_WITH_AES_256_CBC_SHA), S_RSA, K_RSA, C_AES, B_256, M_SHA, F_FIPS_STD, A_RSAD, ssl_hash_none }, - { 0, CS(DHE_RSA_WITH_CAMELLIA_128_CBC_SHA), S_RSA, K_DHE, C_CAMELLIA, B_128, M_SHA, F_NFIPS_STD, A_RSAS }, - { 0, CS(DHE_DSS_WITH_CAMELLIA_128_CBC_SHA), S_DSA, K_DHE, C_CAMELLIA, B_128, M_SHA, F_NFIPS_STD, A_DSA }, - { 0, CS(DHE_DSS_WITH_RC4_128_SHA), S_DSA, K_DHE, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_DSA }, - { 0, CS(DHE_RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_DHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_RSAS }, - { 0, CS(DHE_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_DHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_RSAS }, - { 0, CS(DHE_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_DHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_RSAS }, - { 0, CS(DHE_DSS_WITH_AES_128_GCM_SHA256), S_DSA, K_DHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_DSA }, - { 0, CS(DHE_DSS_WITH_AES_128_CBC_SHA), S_DSA, K_DHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_DSA }, - { 0, CS(DHE_DSS_WITH_AES_128_CBC_SHA256), S_DSA, K_DHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_DSA }, - { 0, CS(RSA_WITH_SEED_CBC_SHA), S_RSA, K_RSA, C_SEED, B_128, M_SHA, F_FIPS_STD, A_RSAD }, - { 0, CS(RSA_WITH_CAMELLIA_128_CBC_SHA), S_RSA, K_RSA, C_CAMELLIA, B_128, M_SHA, F_NFIPS_STD, A_RSAD }, - { 0, CS(RSA_WITH_RC4_128_SHA), S_RSA, K_RSA, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_RSAD }, - { 0, CS(RSA_WITH_RC4_128_MD5), S_RSA, K_RSA, C_RC4, B_128, M_MD5, F_NFIPS_STD, A_RSAD }, - { 0, CS(RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_RSA, C_AES, B_128, M_SHA256, F_FIPS_STD, A_RSAD }, - { 0, CS(RSA_WITH_AES_128_CBC_SHA), S_RSA, K_RSA, C_AES, B_128, M_SHA, F_FIPS_STD, A_RSAD }, + { 0, CS(DHE_RSA_WITH_CAMELLIA_128_CBC_SHA), S_RSA, K_DHE, C_CAMELLIA, B_128, M_SHA, F_NFIPS_STD, A_RSAS, ssl_hash_none }, + { 0, CS(DHE_DSS_WITH_CAMELLIA_128_CBC_SHA), S_DSA, K_DHE, C_CAMELLIA, B_128, M_SHA, F_NFIPS_STD, A_DSA, ssl_hash_none }, + { 0, CS(DHE_DSS_WITH_RC4_128_SHA), S_DSA, K_DHE, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_DSA, ssl_hash_none }, + { 0, CS(DHE_RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_DHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_RSAS, ssl_hash_sha256 }, + { 0, CS(DHE_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_DHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_RSAS, ssl_hash_sha256 }, + { 0, CS(DHE_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_DHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_RSAS, ssl_hash_none }, + { 0, CS(DHE_DSS_WITH_AES_128_GCM_SHA256), S_DSA, K_DHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_DSA, ssl_hash_sha256 }, + { 0, CS(DHE_DSS_WITH_AES_128_CBC_SHA), S_DSA, K_DHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_DSA, ssl_hash_none }, + { 0, CS(DHE_DSS_WITH_AES_128_CBC_SHA256), S_DSA, K_DHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_DSA, ssl_hash_sha256 }, + { 0, CS(RSA_WITH_SEED_CBC_SHA), S_RSA, K_RSA, C_SEED, B_128, M_SHA, F_FIPS_STD, A_RSAD, ssl_hash_none }, + { 0, CS(RSA_WITH_CAMELLIA_128_CBC_SHA), S_RSA, K_RSA, C_CAMELLIA, B_128, M_SHA, F_NFIPS_STD, A_RSAD, ssl_hash_none }, + { 0, CS(RSA_WITH_RC4_128_SHA), S_RSA, K_RSA, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_RSAD, ssl_hash_none }, + { 0, CS(RSA_WITH_RC4_128_MD5), S_RSA, K_RSA, C_RC4, B_128, M_MD5, F_NFIPS_STD, A_RSAD, ssl_hash_none }, + { 0, CS(RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_RSA, C_AES, B_128, M_SHA256, F_FIPS_STD, A_RSAD, ssl_hash_sha256 }, + { 0, CS(RSA_WITH_AES_128_CBC_SHA), S_RSA, K_RSA, C_AES, B_128, M_SHA, F_FIPS_STD, A_RSAD, ssl_hash_none }, - { 0, CS(DHE_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_DHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_RSAS }, - { 0, CS(DHE_DSS_WITH_3DES_EDE_CBC_SHA), S_DSA, K_DHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_DSA }, - { 0, CS(RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_RSA, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_RSAD }, + { 0, CS(DHE_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_DHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_RSAS, ssl_hash_none }, + { 0, CS(DHE_DSS_WITH_3DES_EDE_CBC_SHA), S_DSA, K_DHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_DSA, ssl_hash_none }, + { 0, CS(RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_RSA, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_RSAD, ssl_hash_none }, - { 0, CS(DHE_RSA_WITH_DES_CBC_SHA), S_RSA, K_DHE, C_DES, B_DES, M_SHA, F_NFIPS_STD, A_RSAS }, - { 0, CS(DHE_DSS_WITH_DES_CBC_SHA), S_DSA, K_DHE, C_DES, B_DES, M_SHA, F_NFIPS_STD, A_DSA }, - { 0, CS(RSA_WITH_DES_CBC_SHA), S_RSA, K_RSA, C_DES, B_DES, M_SHA, F_NFIPS_STD, A_RSAD }, + { 0, CS(DHE_RSA_WITH_DES_CBC_SHA), S_RSA, K_DHE, C_DES, B_DES, M_SHA, F_NFIPS_STD, A_RSAS, ssl_hash_none }, + { 0, CS(DHE_DSS_WITH_DES_CBC_SHA), S_DSA, K_DHE, C_DES, B_DES, M_SHA, F_NFIPS_STD, A_DSA, ssl_hash_none }, + { 0, CS(RSA_WITH_DES_CBC_SHA), S_RSA, K_RSA, C_DES, B_DES, M_SHA, F_NFIPS_STD, A_RSAD, ssl_hash_none }, - { 0, CS(RSA_WITH_NULL_SHA256), S_RSA, K_RSA, C_NULL, B_0, M_SHA256, F_EXPORT, A_RSAD }, - { 0, CS(RSA_WITH_NULL_SHA), S_RSA, K_RSA, C_NULL, B_0, M_SHA, F_EXPORT, A_RSAD }, - { 0, CS(RSA_WITH_NULL_MD5), S_RSA, K_RSA, C_NULL, B_0, M_MD5, F_EXPORT, A_RSAD }, + { 0, CS(RSA_WITH_NULL_SHA256), S_RSA, K_RSA, C_NULL, B_0, M_SHA256, F_EXPORT, A_RSAD, ssl_hash_sha256 }, + { 0, CS(RSA_WITH_NULL_SHA), S_RSA, K_RSA, C_NULL, B_0, M_SHA, F_EXPORT, A_RSAD, ssl_hash_none }, + { 0, CS(RSA_WITH_NULL_MD5), S_RSA, K_RSA, C_NULL, B_0, M_MD5, F_EXPORT, A_RSAD, ssl_hash_none }, /* ECC cipher suites */ - { 0, CS(ECDHE_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_ECDHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_RSAS }, - { 0, CS(ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), S_ECDSA, K_ECDHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_ECDSA }, - { 0, CS(ECDH_ECDSA_WITH_NULL_SHA), S_ECDSA, K_ECDH, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_ECDH_E }, - { 0, CS(ECDH_ECDSA_WITH_RC4_128_SHA), S_ECDSA, K_ECDH, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_ECDH_E }, - { 0, CS(ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA), S_ECDSA, K_ECDH, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_ECDH_E }, - { 0, CS(ECDH_ECDSA_WITH_AES_128_CBC_SHA), S_ECDSA, K_ECDH, C_AES, B_128, M_SHA, F_FIPS_STD, A_ECDH_E }, - { 0, CS(ECDH_ECDSA_WITH_AES_256_CBC_SHA), S_ECDSA, K_ECDH, C_AES, B_256, M_SHA, F_FIPS_STD, A_ECDH_E }, + { 0, CS(ECDHE_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_ECDHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_RSAS, ssl_hash_sha256 }, + { 0, CS(ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), S_ECDSA, K_ECDHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_ECDSA, ssl_hash_sha256 }, + { 0, CS(ECDH_ECDSA_WITH_NULL_SHA), S_ECDSA, K_ECDH, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_ECDH_E, ssl_hash_none }, + { 0, CS(ECDH_ECDSA_WITH_RC4_128_SHA), S_ECDSA, K_ECDH, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_ECDH_E, ssl_hash_none }, + { 0, CS(ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA), S_ECDSA, K_ECDH, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_ECDH_E, ssl_hash_none }, + { 0, CS(ECDH_ECDSA_WITH_AES_128_CBC_SHA), S_ECDSA, K_ECDH, C_AES, B_128, M_SHA, F_FIPS_STD, A_ECDH_E, ssl_hash_none }, + { 0, CS(ECDH_ECDSA_WITH_AES_256_CBC_SHA), S_ECDSA, K_ECDH, C_AES, B_256, M_SHA, F_FIPS_STD, A_ECDH_E, ssl_hash_none }, - { 0, CS(ECDHE_ECDSA_WITH_NULL_SHA), S_ECDSA, K_ECDHE, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_ECDSA }, - { 0, CS(ECDHE_ECDSA_WITH_RC4_128_SHA), S_ECDSA, K_ECDHE, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_ECDSA }, - { 0, CS(ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA), S_ECDSA, K_ECDHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_ECDSA }, - { 0, CS(ECDHE_ECDSA_WITH_AES_128_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_ECDSA }, - { 0, CS(ECDHE_ECDSA_WITH_AES_128_CBC_SHA256), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_ECDSA }, - { 0, CS(ECDHE_ECDSA_WITH_AES_256_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_ECDSA }, - { 0, CS(ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256), S_ECDSA, K_ECDHE, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_ECDSA }, + { 0, CS(ECDHE_ECDSA_WITH_NULL_SHA), S_ECDSA, K_ECDHE, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_ECDSA, ssl_hash_none }, + { 0, CS(ECDHE_ECDSA_WITH_RC4_128_SHA), S_ECDSA, K_ECDHE, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_ECDSA, ssl_hash_none }, + { 0, CS(ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA), S_ECDSA, K_ECDHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_ECDSA, ssl_hash_none }, + { 0, CS(ECDHE_ECDSA_WITH_AES_128_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_ECDSA, ssl_hash_none }, + { 0, CS(ECDHE_ECDSA_WITH_AES_128_CBC_SHA256), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_ECDSA, ssl_hash_sha256 }, + { 0, CS(ECDHE_ECDSA_WITH_AES_256_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_ECDSA, ssl_hash_none }, + { 0, CS(ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256), S_ECDSA, K_ECDHE, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_ECDSA, ssl_hash_sha256 }, - { 0, CS(ECDH_RSA_WITH_NULL_SHA), S_RSA, K_ECDH, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_ECDH_R }, - { 0, CS(ECDH_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDH, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_ECDH_R }, - { 0, CS(ECDH_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_ECDH, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_ECDH_R }, - { 0, CS(ECDH_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_ECDH, C_AES, B_128, M_SHA, F_FIPS_STD, A_ECDH_R }, - { 0, CS(ECDH_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_ECDH, C_AES, B_256, M_SHA, F_FIPS_STD, A_ECDH_R }, + { 0, CS(ECDH_RSA_WITH_NULL_SHA), S_RSA, K_ECDH, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_ECDH_R, ssl_hash_none }, + { 0, CS(ECDH_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDH, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_ECDH_R, ssl_hash_none }, + { 0, CS(ECDH_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_ECDH, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_ECDH_R, ssl_hash_none }, + { 0, CS(ECDH_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_ECDH, C_AES, B_128, M_SHA, F_FIPS_STD, A_ECDH_R, ssl_hash_none }, + { 0, CS(ECDH_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_ECDH, C_AES, B_256, M_SHA, F_FIPS_STD, A_ECDH_R, ssl_hash_none }, - { 0, CS(ECDHE_RSA_WITH_NULL_SHA), S_RSA, K_ECDHE, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_RSAS }, - { 0, CS(ECDHE_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDHE, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_RSAS }, - { 0, CS(ECDHE_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_ECDHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_RSAS }, - { 0, CS(ECDHE_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_ECDHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_RSAS }, - { 0, CS(ECDHE_RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_ECDHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_RSAS }, - { 0, CS(ECDHE_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_ECDHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_RSAS }, - { 0, CS(ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), S_RSA, K_ECDHE, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_RSAS }, - { 0, CS(ECDHE_RSA_WITH_AES_256_CBC_SHA384), S_RSA, K_ECDHE, C_AES, B_256, M_SHA384, F_FIPS_STD, A_RSAS }, - { 0, CS(ECDHE_ECDSA_WITH_AES_256_CBC_SHA384), S_ECDSA, K_ECDHE, C_AES, B_256, M_SHA384, F_FIPS_STD, A_ECDSA }, - { 0, CS(ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), S_ECDSA, K_ECDHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_ECDSA }, - { 0, CS(ECDHE_RSA_WITH_AES_256_GCM_SHA384), S_RSA, K_ECDHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_RSAS }, + { 0, CS(ECDHE_RSA_WITH_NULL_SHA), S_RSA, K_ECDHE, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_RSAS, ssl_hash_none }, + { 0, CS(ECDHE_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDHE, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_RSAS, ssl_hash_none }, + { 0, CS(ECDHE_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_ECDHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_RSAS, ssl_hash_none }, + { 0, CS(ECDHE_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_ECDHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_RSAS, ssl_hash_none }, + { 0, CS(ECDHE_RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_ECDHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_RSAS, ssl_hash_sha256 }, + { 0, CS(ECDHE_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_ECDHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_RSAS, ssl_hash_none }, + { 0, CS(ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), S_RSA, K_ECDHE, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_RSAS, ssl_hash_sha256 }, + { 0, CS(ECDHE_RSA_WITH_AES_256_CBC_SHA384), S_RSA, K_ECDHE, C_AES, B_256, M_SHA384, F_FIPS_STD, A_RSAS, ssl_hash_sha384 }, + { 0, CS(ECDHE_ECDSA_WITH_AES_256_CBC_SHA384), S_ECDSA, K_ECDHE, C_AES, B_256, M_SHA384, F_FIPS_STD, A_ECDSA, ssl_hash_sha384 }, + { 0, CS(ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), S_ECDSA, K_ECDHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_ECDSA, ssl_hash_sha384 }, + { 0, CS(ECDHE_RSA_WITH_AES_256_GCM_SHA384), S_RSA, K_ECDHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_RSAS, ssl_hash_sha384 }, - { 0, CS(DHE_DSS_WITH_AES_256_GCM_SHA384), S_DSA, K_DHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_DSA }, - { 0, CS(DHE_RSA_WITH_AES_256_GCM_SHA384), S_RSA, K_DHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_RSAS }, - { 0, CS(RSA_WITH_AES_256_GCM_SHA384), S_RSA, K_RSA, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_RSAD }, + { 0, CS(DHE_DSS_WITH_AES_256_GCM_SHA384), S_DSA, K_DHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_DSA, ssl_hash_sha384 }, + { 0, CS(DHE_RSA_WITH_AES_256_GCM_SHA384), S_RSA, K_DHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_RSAS, ssl_hash_sha384 }, + { 0, CS(RSA_WITH_AES_256_GCM_SHA384), S_RSA, K_RSA, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_RSAD, ssl_hash_sha384 }, }; #define NUM_SUITEINFOS ((sizeof suiteInfo) / (sizeof suiteInfo[0])) diff --git a/security/nss/lib/ssl/sslsecur.c b/security/nss/lib/ssl/sslsecur.c index c011b66a11c3..79634e40398e 100644 --- a/security/nss/lib/ssl/sslsecur.c +++ b/security/nss/lib/ssl/sslsecur.c @@ -16,32 +16,7 @@ #include "nss.h" /* for NSS_RegisterShutdown */ #include "prinit.h" /* for PR_CallOnceWithArg */ -/* Returns a SECStatus: SECSuccess or SECFailure, NOT SECWouldBlock. - * - * Currently, the list of functions called through ss->handshake is: - * - * In sslsocks.c: - * SocksGatherRecord - * SocksHandleReply - * SocksStartGather - * - * In sslcon.c: - * ssl_GatherRecord1stHandshake - * ssl_BeginClientHandshake - * ssl_BeginServerHandshake - * - * The ss->handshake function returns SECWouldBlock if it was returned by - * one of the callback functions, via one of these paths: - * - * - ssl_GatherRecord1stHandshake() -> ssl3_GatherCompleteHandshake() -> - * ssl3_HandleRecord() -> ssl3_HandleHandshake() -> - * ssl3_HandleHandshakeMessage() -> ssl3_HandleCertificate() -> - * ss->handleBadCert() - * - * - ssl_GatherRecord1stHandshake() -> ssl3_GatherCompleteHandshake() -> - * ssl3_HandleRecord() -> ssl3_HandleHandshake() -> - * ssl3_HandleHandshakeMessage() -> ssl3_HandleCertificateRequest() -> - * ss->getClientAuthData() +/* Step through the handshake functions. * * Called from: SSL_ForceHandshake (below), * ssl_SecureRecv (below) and @@ -52,10 +27,10 @@ * * Caller must hold the (write) handshakeLock. */ -int +SECStatus ssl_Do1stHandshake(sslSocket *ss) { - int rv = SECSuccess; + SECStatus rv = SECSuccess; while (ss->handshake && rv == SECSuccess) { PORT_Assert(ss->opt.noLocks || ssl_Have1stHandshakeLock(ss)); @@ -70,10 +45,6 @@ ssl_Do1stHandshake(sslSocket *ss) PORT_Assert(ss->opt.noLocks || !ssl_HaveXmitBufLock(ss)); PORT_Assert(ss->opt.noLocks || !ssl_HaveSSL3HandshakeLock(ss)); - if (rv == SECWouldBlock) { - PORT_SetError(PR_WOULD_BLOCK_ERROR); - rv = SECFailure; - } return rv; } @@ -106,8 +77,8 @@ ssl_FinishHandshake(sslSocket *ss) static SECStatus ssl3_AlwaysBlock(sslSocket *ss) { - PORT_SetError(PR_WOULD_BLOCK_ERROR); /* perhaps redundant. */ - return SECWouldBlock; + PORT_SetError(PR_WOULD_BLOCK_ERROR); + return SECFailure; } /* @@ -400,10 +371,13 @@ SSL_ForceHandshake(PRFileDesc *fd) ssl_ReleaseRecvBufLock(ss); if (gatherResult > 0) { rv = SECSuccess; - } else if (gatherResult == 0) { - PORT_SetError(PR_END_OF_FILE_ERROR); - } else if (gatherResult == SECWouldBlock) { - PORT_SetError(PR_WOULD_BLOCK_ERROR); + } else { + if (gatherResult == 0) { + PORT_SetError(PR_END_OF_FILE_ERROR); + } + /* We can rely on ssl3_GatherCompleteHandshake to set + * PR_WOULD_BLOCK_ERROR as needed here. */ + rv = SECFailure; } } else { PORT_Assert(!ss->firstHsDone); @@ -515,8 +489,7 @@ DoRecv(sslSocket *ss, unsigned char *out, int len, int flags) SSL_GETPID(), ss->fd)); goto done; } - if ((rv != SECWouldBlock) && - (PR_GetError() != PR_WOULD_BLOCK_ERROR)) { + if (PR_GetError() != PR_WOULD_BLOCK_ERROR) { /* Some random error */ goto done; } @@ -741,7 +714,7 @@ ssl_SecureShutdown(sslSocket *ss, int nsprHow) /************************************************************************/ static SECStatus -tls13_CheckKeyUpdate(sslSocket *ss, CipherSpecDirection dir) +tls13_CheckKeyUpdate(sslSocket *ss, SSLSecretDirection dir) { PRBool keyUpdate; ssl3CipherSpec *spec; @@ -765,7 +738,7 @@ tls13_CheckKeyUpdate(sslSocket *ss, CipherSpecDirection dir) * having the write margin larger reduces the number of times that a * KeyUpdate is sent by a reader. */ ssl_GetSpecReadLock(ss); - if (dir == CipherSpecRead) { + if (dir == ssl_secret_read) { spec = ss->ssl3.crSpec; margin = spec->cipherDef->max_records / 8; } else { @@ -781,10 +754,10 @@ tls13_CheckKeyUpdate(sslSocket *ss, CipherSpecDirection dir) SSL_TRC(5, ("%d: SSL[%d]: automatic key update at %llx for %s cipher spec", SSL_GETPID(), ss->fd, seqNum, - (dir == CipherSpecRead) ? "read" : "write")); + (dir == ssl_secret_read) ? "read" : "write")); ssl_GetSSL3HandshakeLock(ss); - rv = tls13_SendKeyUpdate(ss, (dir == CipherSpecRead) ? update_requested : update_not_requested, - dir == CipherSpecWrite /* buffer */); + rv = tls13_SendKeyUpdate(ss, (dir == ssl_secret_read) ? update_requested : update_not_requested, + dir == ssl_secret_write /* buffer */); ssl_ReleaseSSL3HandshakeLock(ss); return rv; } @@ -829,7 +802,7 @@ ssl_SecureRecv(sslSocket *ss, unsigned char *buf, int len, int flags) } ssl_Release1stHandshakeLock(ss); } else { - if (tls13_CheckKeyUpdate(ss, CipherSpecRead) != SECSuccess) { + if (tls13_CheckKeyUpdate(ss, ssl_secret_read) != SECSuccess) { rv = PR_FAILURE; } } @@ -955,7 +928,7 @@ ssl_SecureSend(sslSocket *ss, const unsigned char *buf, int len, int flags) } if (ss->firstHsDone) { - if (tls13_CheckKeyUpdate(ss, CipherSpecWrite) != SECSuccess) { + if (tls13_CheckKeyUpdate(ss, ssl_secret_write) != SECSuccess) { rv = PR_FAILURE; goto done; } @@ -1010,6 +983,35 @@ ssl_SecureWrite(sslSocket *ss, const unsigned char *buf, int len) return ssl_SecureSend(ss, buf, len, 0); } +SECStatus +SSLExp_RecordLayerWriteCallback(PRFileDesc *fd, SSLRecordWriteCallback cb, + void *arg) +{ + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + SSL_DBG(("%d: SSL[%d]: invalid socket for SSL_RecordLayerWriteCallback", + SSL_GETPID(), fd)); + return SECFailure; + } + if (IS_DTLS(ss)) { + SSL_DBG(("%d: SSL[%d]: DTLS socket for SSL_RecordLayerWriteCallback", + SSL_GETPID(), fd)); + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + /* This needs both HS and Xmit locks because this value is checked under + * both locks. HS to disable reading from the underlying IO layer; Xmit to + * prevent writing. */ + ssl_GetSSL3HandshakeLock(ss); + ssl_GetXmitBufLock(ss); + ss->recordWriteCallback = cb; + ss->recordWriteCallbackArg = arg; + ssl_ReleaseXmitBufLock(ss); + ssl_ReleaseSSL3HandshakeLock(ss); + return SECSuccess; +} + SECStatus SSL_AlertReceivedCallback(PRFileDesc *fd, SSLAlertCallback cb, void *arg) { diff --git a/security/nss/lib/ssl/sslsock.c b/security/nss/lib/ssl/sslsock.c index 00855be0041a..b8b75dc9cdbf 100644 --- a/security/nss/lib/ssl/sslsock.c +++ b/security/nss/lib/ssl/sslsock.c @@ -86,7 +86,8 @@ static sslOptions ssl_defaults = { .enableTls13CompatMode = PR_FALSE, .enableDtlsShortHeader = PR_FALSE, .enableHelloDowngradeCheck = PR_FALSE, - .enableV2CompatibleHello = PR_FALSE + .enableV2CompatibleHello = PR_FALSE, + .enablePostHandshakeAuth = PR_FALSE }; /* @@ -842,6 +843,10 @@ SSL_OptionSet(PRFileDesc *fd, PRInt32 which, PRIntn val) ss->opt.enableV2CompatibleHello = val; break; + case SSL_ENABLE_POST_HANDSHAKE_AUTH: + ss->opt.enablePostHandshakeAuth = val; + break; + default: PORT_SetError(SEC_ERROR_INVALID_ARGS); rv = SECFailure; @@ -990,6 +995,9 @@ SSL_OptionGet(PRFileDesc *fd, PRInt32 which, PRIntn *pVal) case SSL_ENABLE_V2_COMPATIBLE_HELLO: val = ss->opt.enableV2CompatibleHello; break; + case SSL_ENABLE_POST_HANDSHAKE_AUTH: + val = ss->opt.enablePostHandshakeAuth; + break; default: PORT_SetError(SEC_ERROR_INVALID_ARGS); rv = SECFailure; @@ -1122,6 +1130,9 @@ SSL_OptionGetDefault(PRInt32 which, PRIntn *pVal) case SSL_ENABLE_V2_COMPATIBLE_HELLO: val = ssl_defaults.enableV2CompatibleHello; break; + case SSL_ENABLE_POST_HANDSHAKE_AUTH: + val = ssl_defaults.enablePostHandshakeAuth; + break; default: PORT_SetError(SEC_ERROR_INVALID_ARGS); rv = SECFailure; @@ -1325,6 +1336,10 @@ SSL_OptionSetDefault(PRInt32 which, PRIntn val) ssl_defaults.enableV2CompatibleHello = val; break; + case SSL_ENABLE_POST_HANDSHAKE_AUTH: + ssl_defaults.enablePostHandshakeAuth = val; + break; + default: PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; @@ -4026,20 +4041,25 @@ struct { void *function; } ssl_experimental_functions[] = { #ifndef SSL_DISABLE_EXPERIMENTAL_API + EXP(DestroyResumptionTokenInfo), + EXP(EnableESNI), + EXP(EncodeESNIKeys), + EXP(GetCurrentEpoch), EXP(GetExtensionSupport), + EXP(GetResumptionTokenInfo), EXP(HelloRetryRequestCallback), EXP(InstallExtensionHooks), EXP(KeyUpdate), + EXP(RecordLayerData), + EXP(RecordLayerWriteCallback), + EXP(SecretCallback), + EXP(SendCertificateRequest), EXP(SendSessionTicket), + EXP(SetESNIKeyPair), EXP(SetMaxEarlyDataSize), - EXP(SetupAntiReplay), EXP(SetResumptionTokenCallback), EXP(SetResumptionToken), - EXP(GetResumptionTokenInfo), - EXP(DestroyResumptionTokenInfo), - EXP(SetESNIKeyPair), - EXP(EncodeESNIKeys), - EXP(EnableESNI), + EXP(SetupAntiReplay), #endif { "", NULL } }; diff --git a/security/nss/lib/ssl/sslspec.c b/security/nss/lib/ssl/sslspec.c index f2e72a4ec465..def3c67505d8 100644 --- a/security/nss/lib/ssl/sslspec.c +++ b/security/nss/lib/ssl/sslspec.c @@ -113,7 +113,7 @@ ssl_GetMacDef(const sslSocket *ss, const ssl3CipherSuiteDef *suiteDef) } ssl3CipherSpec * -ssl_FindCipherSpecByEpoch(sslSocket *ss, CipherSpecDirection direction, +ssl_FindCipherSpecByEpoch(sslSocket *ss, SSLSecretDirection direction, DTLSEpoch epoch) { PRCList *cur_p; @@ -134,7 +134,7 @@ ssl_FindCipherSpecByEpoch(sslSocket *ss, CipherSpecDirection direction, } ssl3CipherSpec * -ssl_CreateCipherSpec(sslSocket *ss, CipherSpecDirection direction) +ssl_CreateCipherSpec(sslSocket *ss, SSLSecretDirection direction) { ssl3CipherSpec *spec = PORT_ZNew(ssl3CipherSpec); if (!spec) { @@ -159,7 +159,7 @@ ssl_SaveCipherSpec(sslSocket *ss, ssl3CipherSpec *spec) /* Called from ssl3_InitState. */ /* Caller must hold the SpecWriteLock. */ SECStatus -ssl_SetupNullCipherSpec(sslSocket *ss, CipherSpecDirection dir) +ssl_SetupNullCipherSpec(sslSocket *ss, SSLSecretDirection dir) { ssl3CipherSpec *spec; @@ -187,7 +187,7 @@ ssl_SetupNullCipherSpec(sslSocket *ss, CipherSpecDirection dir) dtls_InitRecvdRecords(&spec->recvdRecords); ssl_SaveCipherSpec(ss, spec); - if (dir == CipherSpecRead) { + if (dir == ssl_secret_read) { ss->ssl3.crSpec = spec; } else { ss->ssl3.cwSpec = spec; @@ -259,13 +259,13 @@ ssl_DestroyCipherSpecs(PRCList *list) } void -ssl_CipherSpecReleaseByEpoch(sslSocket *ss, CipherSpecDirection dir, +ssl_CipherSpecReleaseByEpoch(sslSocket *ss, SSLSecretDirection dir, DTLSEpoch epoch) { ssl3CipherSpec *spec; SSL_TRC(10, ("%d: SSL[%d]: releasing %s cipher spec for epoch %d", SSL_GETPID(), ss->fd, - (dir == CipherSpecRead) ? "read" : "write", epoch)); + (dir == ssl_secret_read) ? "read" : "write", epoch)); spec = ssl_FindCipherSpecByEpoch(ss, dir, epoch); if (spec) { diff --git a/security/nss/lib/ssl/sslspec.h b/security/nss/lib/ssl/sslspec.h index b25601755318..967e35862cd5 100644 --- a/security/nss/lib/ssl/sslspec.h +++ b/security/nss/lib/ssl/sslspec.h @@ -19,13 +19,8 @@ typedef enum { TrafficKeyApplicationData = 3 } TrafficKeyType; -typedef enum { - CipherSpecRead, - CipherSpecWrite, -} CipherSpecDirection; - #define SPEC_DIR(spec) \ - ((spec->direction == CipherSpecRead) ? "read" : "write") + ((spec->direction == ssl_secret_read) ? "read" : "write") typedef struct ssl3CipherSpecStr ssl3CipherSpec; typedef struct ssl3BulkCipherDefStr ssl3BulkCipherDef; @@ -146,7 +141,7 @@ struct ssl3CipherSpecStr { PRCList link; PRUint8 refCt; - CipherSpecDirection direction; + SSLSecretDirection direction; SSL3ProtocolVersion version; SSL3ProtocolVersion recordVersion; @@ -184,17 +179,17 @@ const ssl3BulkCipherDef *ssl_GetBulkCipherDef(const ssl3CipherSuiteDef *cipher_d const ssl3MACDef *ssl_GetMacDefByAlg(SSL3MACAlgorithm mac); const ssl3MACDef *ssl_GetMacDef(const sslSocket *ss, const ssl3CipherSuiteDef *suiteDef); -ssl3CipherSpec *ssl_CreateCipherSpec(sslSocket *ss, CipherSpecDirection direction); +ssl3CipherSpec *ssl_CreateCipherSpec(sslSocket *ss, SSLSecretDirection direction); void ssl_SaveCipherSpec(sslSocket *ss, ssl3CipherSpec *spec); void ssl_CipherSpecAddRef(ssl3CipherSpec *spec); void ssl_CipherSpecRelease(ssl3CipherSpec *spec); void ssl_DestroyCipherSpecs(PRCList *list); -SECStatus ssl_SetupNullCipherSpec(sslSocket *ss, CipherSpecDirection dir); +SECStatus ssl_SetupNullCipherSpec(sslSocket *ss, SSLSecretDirection dir); ssl3CipherSpec *ssl_FindCipherSpecByEpoch(sslSocket *ss, - CipherSpecDirection direction, + SSLSecretDirection direction, DTLSEpoch epoch); -void ssl_CipherSpecReleaseByEpoch(sslSocket *ss, CipherSpecDirection direction, +void ssl_CipherSpecReleaseByEpoch(sslSocket *ss, SSLSecretDirection direction, DTLSEpoch epoch); #endif /* __sslspec_h_ */ diff --git a/security/nss/lib/ssl/sslt.h b/security/nss/lib/ssl/sslt.h index bd32a6e18001..d389a6a2e152 100644 --- a/security/nss/lib/ssl/sslt.h +++ b/security/nss/lib/ssl/sslt.h @@ -43,6 +43,11 @@ typedef enum { ssl_ct_ack = 25 } SSLContentType; +typedef enum { + ssl_secret_read = 1, + ssl_secret_write = 2, +} SSLSecretDirection; + typedef struct SSL3StatisticsStr { /* statistics from ssl3_SendClientHello (sch) */ long sch_sid_cache_hits; @@ -328,6 +333,9 @@ typedef struct SSLChannelInfoStr { /* Preliminary channel info */ #define ssl_preinfo_version (1U << 0) #define ssl_preinfo_cipher_suite (1U << 1) +#define ssl_preinfo_0rtt_cipher_suite (1U << 2) +/* ssl_preinfo_all doesn't contain ssl_preinfo_0rtt_cipher_suite because that + * field is only set if 0-RTT is sent (client) or accepted (server). */ #define ssl_preinfo_all (ssl_preinfo_version | ssl_preinfo_cipher_suite) typedef struct SSLPreliminaryChannelInfoStr { @@ -359,6 +367,13 @@ typedef struct SSLPreliminaryChannelInfoStr { * resume this session. */ PRUint32 maxEarlyDataSize; + /* The following fields were added in NSS 3.39. */ + /* This reports the cipher suite used for 0-RTT if it sent or accepted. For + * a client, this is set earlier than |cipherSuite|, and will match that + * value if 0-RTT is accepted by the server. The server only sets this + * after accepting 0-RTT, so this will contain the same value. */ + PRUint16 zeroRttCipherSuite; + /* When adding new fields to this structure, please document the * NSS version in which they were added. */ } SSLPreliminaryChannelInfo; @@ -407,6 +422,12 @@ typedef struct SSLCipherSuiteInfoStr { * this instead of |authAlgorithm|. */ SSLAuthType authType; + /* The following fields were added in NSS 3.39. */ + /* This reports the hash function used in the TLS KDF, or HKDF for TLS 1.3. + * For suites defined for versions of TLS earlier than TLS 1.2, this reports + * ssl_hash_none. */ + SSLHashType kdfHash; + /* When adding new fields to this structure, please document the * NSS version in which they were added. */ } SSLCipherSuiteInfo; @@ -450,6 +471,7 @@ typedef enum { ssl_tls13_psk_key_exchange_modes_xtn = 45, ssl_tls13_ticket_early_data_info_xtn = 46, /* Deprecated. */ ssl_tls13_certificate_authorities_xtn = 47, + ssl_tls13_post_handshake_auth_xtn = 49, ssl_signature_algorithms_cert_xtn = 50, ssl_tls13_key_share_xtn = 51, ssl_next_proto_nego_xtn = 13172, /* Deprecated. */ diff --git a/security/nss/lib/ssl/tls13con.c b/security/nss/lib/ssl/tls13con.c index 10c449803385..5daa48951c11 100644 --- a/security/nss/lib/ssl/tls13con.c +++ b/security/nss/lib/ssl/tls13con.c @@ -26,7 +26,7 @@ #include "tls13hashstate.h" static SECStatus tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch, - CipherSpecDirection install, + SSLSecretDirection install, PRBool deleteSecret); static SECStatus tls13_AESGCM( ssl3KeyMaterial *keys, @@ -56,6 +56,7 @@ static SECStatus tls13_SendCertificate(sslSocket *ss); static SECStatus tls13_HandleCertificate( sslSocket *ss, PRUint8 *b, PRUint32 length); static SECStatus tls13_ReinjectHandshakeTranscript(sslSocket *ss); +static SECStatus tls13_SendCertificateRequest(sslSocket *ss); static SECStatus tls13_HandleCertificateRequest(sslSocket *ss, PRUint8 *b, PRUint32 length); static SECStatus @@ -104,6 +105,9 @@ static SECStatus tls13_ComputeFinished( PRBool sending, PRUint8 *output, unsigned int *outputLen, unsigned int maxOutputLen); static SECStatus tls13_SendClientSecondRound(sslSocket *ss); +static SECStatus tls13_SendClientSecondFlight(sslSocket *ss, + PRBool sendClientCert, + SSL3AlertDescription *sendAlert); static SECStatus tls13_FinishHandshake(sslSocket *ss); const char kHkdfLabelClient[] = "c"; @@ -588,13 +592,13 @@ loser: } static PRBool -tls13_UseServerSecret(sslSocket *ss, CipherSpecDirection direction) +tls13_UseServerSecret(sslSocket *ss, SSLSecretDirection direction) { - return ss->sec.isServer == (direction == CipherSpecWrite); + return ss->sec.isServer == (direction == ssl_secret_write); } static PK11SymKey ** -tls13_TrafficSecretRef(sslSocket *ss, CipherSpecDirection direction) +tls13_TrafficSecretRef(sslSocket *ss, SSLSecretDirection direction) { if (tls13_UseServerSecret(ss, direction)) { return &ss->ssl3.hs.serverTrafficSecret; @@ -603,7 +607,7 @@ tls13_TrafficSecretRef(sslSocket *ss, CipherSpecDirection direction) } SECStatus -tls13_UpdateTrafficKeys(sslSocket *ss, CipherSpecDirection direction) +tls13_UpdateTrafficKeys(sslSocket *ss, SSLSecretDirection direction) { PK11SymKey **secret; PK11SymKey *updatedSecret; @@ -626,7 +630,7 @@ tls13_UpdateTrafficKeys(sslSocket *ss, CipherSpecDirection direction) *secret = updatedSecret; ssl_GetSpecReadLock(ss); - if (direction == CipherSpecRead) { + if (direction == ssl_secret_read) { epoch = ss->ssl3.crSpec->epoch; } else { epoch = ss->ssl3.cwSpec->epoch; @@ -640,6 +644,11 @@ tls13_UpdateTrafficKeys(sslSocket *ss, CipherSpecDirection direction) } ++epoch; + if (ss->secretCallback) { + ss->secretCallback(ss->fd, epoch, direction, updatedSecret, + ss->secretCallbackArg); + } + rv = tls13_SetCipherSpec(ss, epoch, direction, PR_FALSE); if (rv != SECSuccess) { FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); @@ -698,7 +707,7 @@ tls13_SendKeyUpdate(sslSocket *ss, tls13KeyUpdateRequest request, PRBool buffer) } ssl_ReleaseXmitBufLock(ss); - rv = tls13_UpdateTrafficKeys(ss, CipherSpecWrite); + rv = tls13_UpdateTrafficKeys(ss, ssl_secret_write); if (rv != SECSuccess) { goto loser; /* error code set by tls13_UpdateTrafficKeys */ } @@ -791,7 +800,7 @@ tls13_HandleKeyUpdate(sslSocket *ss, PRUint8 *b, unsigned int length) return SECFailure; } - rv = tls13_UpdateTrafficKeys(ss, CipherSpecRead); + rv = tls13_UpdateTrafficKeys(ss, ssl_secret_read); if (rv != SECSuccess) { return SECFailure; /* Error code set by tls13_UpdateTrafficKeys. */ } @@ -820,6 +829,56 @@ tls13_HandleKeyUpdate(sslSocket *ss, PRUint8 *b, unsigned int length) return SECSuccess; } +SECStatus +SSLExp_SendCertificateRequest(PRFileDesc *fd) +{ + SECStatus rv; + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + return SECFailure; + } + + /* Not supported. */ + if (IS_DTLS(ss)) { + PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_VERSION); + return SECFailure; + } + + if (!ss->firstHsDone || ss->version < SSL_LIBRARY_VERSION_TLS_1_3) { + PORT_SetError(SEC_ERROR_INVALID_ARGS); + return SECFailure; + } + + if (ss->ssl3.clientCertRequested) { + PORT_SetError(PR_WOULD_BLOCK_ERROR); + return SECFailure; + } + + rv = TLS13_CHECK_HS_STATE(ss, SEC_ERROR_INVALID_ARGS, + idle_handshake); + if (rv != SECSuccess) { + return SECFailure; + } + + if (!ssl3_ExtensionNegotiated(ss, ssl_tls13_post_handshake_auth_xtn)) { + PORT_SetError(SSL_ERROR_MISSING_POST_HANDSHAKE_AUTH_EXTENSION); + return SECFailure; + } + + ssl_GetSSL3HandshakeLock(ss); + + rv = tls13_SendCertificateRequest(ss); + if (rv == SECSuccess) { + ssl_GetXmitBufLock(ss); + rv = ssl3_FlushHandshake(ss, 0); + ssl_ReleaseXmitBufLock(ss); + ss->ssl3.clientCertRequested = PR_TRUE; + } + + ssl_ReleaseSSL3HandshakeLock(ss); + return rv; +} + SECStatus tls13_HandlePostHelloHandshakeMessage(sslSocket *ss, PRUint8 *b, PRUint32 length) { @@ -1030,6 +1089,13 @@ tls13_DeriveEarlySecrets(sslSocket *ss) return SECFailure; } + if (ss->secretCallback) { + ss->secretCallback(ss->fd, (PRUint16)TrafficKeyEarlyApplicationData, + ss->sec.isServer ? ssl_secret_read : ssl_secret_write, + ss->ssl3.hs.clientEarlyTrafficSecret, + ss->secretCallbackArg); + } + rv = tls13_DeriveSecretWrap(ss, ss->ssl3.hs.currentSecret, NULL, kHkdfLabelEarlyExporterSecret, keylogLabelEarlyExporterSecret, @@ -1098,6 +1164,18 @@ tls13_ComputeHandshakeSecrets(sslSocket *ss) return rv; } + if (ss->secretCallback) { + SSLSecretDirection dir = + ss->sec.isServer ? ssl_secret_read : ssl_secret_write; + ss->secretCallback(ss->fd, (PRUint16)TrafficKeyHandshake, dir, + ss->ssl3.hs.clientHsTrafficSecret, + ss->secretCallbackArg); + dir = ss->sec.isServer ? ssl_secret_write : ssl_secret_read; + ss->secretCallback(ss->fd, (PRUint16)TrafficKeyHandshake, dir, + ss->ssl3.hs.serverHsTrafficSecret, + ss->secretCallbackArg); + } + SSL_TRC(5, ("%d: TLS13[%d]: compute master secret (%s)", SSL_GETPID(), ss->fd, SSL_ROLE(ss))); @@ -1148,6 +1226,18 @@ tls13_ComputeApplicationSecrets(sslSocket *ss) return SECFailure; } + if (ss->secretCallback) { + SSLSecretDirection dir = + ss->sec.isServer ? ssl_secret_read : ssl_secret_write; + ss->secretCallback(ss->fd, (PRUint16)TrafficKeyApplicationData, + dir, ss->ssl3.hs.clientTrafficSecret, + ss->secretCallbackArg); + dir = ss->sec.isServer ? ssl_secret_write : ssl_secret_read; + ss->secretCallback(ss->fd, (PRUint16)TrafficKeyApplicationData, + dir, ss->ssl3.hs.serverTrafficSecret, + ss->secretCallbackArg); + } + rv = tls13_DeriveSecretWrap(ss, ss->ssl3.hs.currentSecret, NULL, kHkdfLabelExporterMasterSecret, keylogLabelExporterSecret, @@ -1294,6 +1384,8 @@ tls13_NegotiateZeroRtt(sslSocket *ss, const sslSessionID *sid) PORT_Assert(ss->statelessResume); ss->ssl3.hs.zeroRttState = ssl_0rtt_accepted; ss->ssl3.hs.zeroRttIgnore = ssl_0rtt_ignore_none; + ss->ssl3.hs.zeroRttSuite = ss->ssl3.hs.cipher_suite; + ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_0rtt_cipher_suite; } /* Check if the offered group is acceptable. */ @@ -2101,8 +2193,27 @@ tls13_SendCertificateRequest(sslSocket *ss) /* We should always have at least one of these. */ PORT_Assert(SSL_BUFFER_LEN(&extensionBuf) > 0); + /* Create a new request context for post-handshake authentication */ + if (ss->firstHsDone) { + PRUint8 context[16]; + SECItem contextItem = { siBuffer, context, sizeof(context) }; + + rv = PK11_GenerateRandom(context, sizeof(context)); + if (rv != SECSuccess) { + goto loser; + } + + SECITEM_FreeItem(&ss->xtnData.certReqContext, PR_FALSE); + rv = SECITEM_CopyItem(NULL, &ss->xtnData.certReqContext, &contextItem); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SEC_ERROR_NO_MEMORY, internal_error); + goto loser; + } + } + rv = ssl3_AppendHandshakeHeader(ss, ssl_hs_certificate_request, - 1 + 0 + /* empty request context */ + 1 + /* request context length */ + ss->xtnData.certReqContext.len + 2 + /* extension length */ SSL_BUFFER_LEN(&extensionBuf)); if (rv != SECSuccess) { @@ -2110,7 +2221,8 @@ tls13_SendCertificateRequest(sslSocket *ss) } /* Context. */ - rv = ssl3_AppendHandshakeNumber(ss, 0, 1); + rv = ssl3_AppendHandshakeVariable(ss, ss->xtnData.certReqContext.data, + ss->xtnData.certReqContext.len, 1); if (rv != SECSuccess) { goto loser; /* err set by AppendHandshake. */ } @@ -2198,7 +2310,7 @@ tls13_HandleHelloRetryRequest(sslSocket *ss, const PRUint8 *savedMsg, /* Restore the null cipher spec for writing. */ ssl_GetSpecWriteLock(ss); ssl_CipherSpecRelease(ss->ssl3.cwSpec); - ss->ssl3.cwSpec = ssl_FindCipherSpecByEpoch(ss, CipherSpecWrite, + ss->ssl3.cwSpec = ssl_FindCipherSpecByEpoch(ss, ssl_secret_write, TrafficKeyClearText); PORT_Assert(ss->ssl3.cwSpec); ssl_ReleaseSpecWriteLock(ss); @@ -2274,25 +2386,49 @@ tls13_HandleCertificateRequest(sslSocket *ss, PRUint8 *b, PRUint32 length) PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); /* Client */ - rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST, - wait_cert_request); + if (ss->opt.enablePostHandshakeAuth) { + rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST, + wait_cert_request, idle_handshake); + } else { + rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST, + wait_cert_request); + } if (rv != SECSuccess) { return SECFailure; } - PORT_Assert(ss->ssl3.clientCertChain == NULL); - PORT_Assert(ss->ssl3.clientCertificate == NULL); - PORT_Assert(ss->ssl3.clientPrivateKey == NULL); - PORT_Assert(!ss->ssl3.hs.clientCertRequested); + if (ss->firstHsDone) { + /* clean up anything left from previous handshake. */ + if (ss->ssl3.clientCertChain != NULL) { + CERT_DestroyCertificateList(ss->ssl3.clientCertChain); + ss->ssl3.clientCertChain = NULL; + } + if (ss->ssl3.clientCertificate != NULL) { + CERT_DestroyCertificate(ss->ssl3.clientCertificate); + ss->ssl3.clientCertificate = NULL; + } + if (ss->ssl3.clientPrivateKey != NULL) { + SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey); + ss->ssl3.clientPrivateKey = NULL; + } + SECITEM_FreeItem(&ss->xtnData.certReqContext, PR_FALSE); + ss->xtnData.certReqContext.data = NULL; + } else { + PORT_Assert(ss->ssl3.clientCertChain == NULL); + PORT_Assert(ss->ssl3.clientCertificate == NULL); + PORT_Assert(ss->ssl3.clientPrivateKey == NULL); + PORT_Assert(!ss->ssl3.hs.clientCertRequested); + PORT_Assert(ss->xtnData.certReqContext.data == NULL); + } rv = ssl3_ConsumeHandshakeVariable(ss, &context, 1, &b, &length); if (rv != SECSuccess) { return SECFailure; } - /* We don't support post-handshake client auth, the certificate request - * context must always be empty. */ - if (context.len > 0) { + /* Unless it is a post-handshake client auth, the certificate + * request context must be empty. */ + if (!ss->firstHsDone && context.len > 0) { FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CERT_REQUEST, illegal_parameter); return SECFailure; } @@ -2326,7 +2462,35 @@ tls13_HandleCertificateRequest(sslSocket *ss, PRUint8 *b, PRUint32 length) } ss->ssl3.hs.clientCertRequested = PR_TRUE; - TLS13_SET_HS_STATE(ss, wait_server_cert); + + if (ss->firstHsDone) { + SSL3AlertDescription sendAlert = no_alert; + + /* Request a client certificate. */ + rv = ssl3_CompleteHandleCertificateRequest( + ss, ss->xtnData.sigSchemes, ss->xtnData.numSigSchemes, + &ss->xtnData.certReqAuthorities); + if (rv != SECSuccess) { + FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); + return rv; + } + + ssl_GetXmitBufLock(ss); + rv = tls13_SendClientSecondFlight(ss, !ss->ssl3.sendEmptyCert, + &sendAlert); + ssl_ReleaseXmitBufLock(ss); + if (rv != SECSuccess) { + if (sendAlert != no_alert) { + FATAL_ERROR(ss, PORT_GetError(), sendAlert); + } else { + LOG_ERROR(ss, PORT_GetError()); + } + return SECFailure; + } + PORT_Assert(ss->ssl3.hs.ws == idle_handshake); + } else { + TLS13_SET_HS_STATE(ss, wait_server_cert); + } return SECSuccess; } @@ -2348,7 +2512,7 @@ tls13_SendEncryptedServerSequence(sslSocket *ss) } rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake, - CipherSpecWrite, PR_FALSE); + ssl_secret_write, PR_FALSE); if (rv != SECSuccess) { LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE); return SECFailure; @@ -2458,7 +2622,7 @@ tls13_SendServerHelloSequence(sslSocket *ss) } rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData, - CipherSpecWrite, PR_FALSE); + ssl_secret_write, PR_FALSE); if (rv != SECSuccess) { LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE); return SECFailure; @@ -2470,7 +2634,7 @@ tls13_SendServerHelloSequence(sslSocket *ss) } if (ss->ssl3.hs.zeroRttState == ssl_0rtt_accepted) { rv = tls13_SetCipherSpec(ss, TrafficKeyEarlyApplicationData, - CipherSpecRead, PR_TRUE); + ssl_secret_read, PR_TRUE); if (rv != SECSuccess) { LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE); return SECFailure; @@ -2482,7 +2646,7 @@ tls13_SendServerHelloSequence(sslSocket *ss) rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake, - CipherSpecRead, PR_FALSE); + ssl_secret_read, PR_FALSE); if (rv != SECSuccess) { LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE); return SECFailure; @@ -2591,11 +2755,11 @@ tls13_HandleServerHelloPart2(sslSocket *ss) /* When we send 0-RTT, we saved the null spec in case we needed it to * send another ClientHello in response to a HelloRetryRequest. Now * that we won't be receiving a HelloRetryRequest, release the spec. */ - ssl_CipherSpecReleaseByEpoch(ss, CipherSpecWrite, TrafficKeyClearText); + ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_write, TrafficKeyClearText); } rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake, - CipherSpecRead, PR_FALSE); + ssl_secret_read, PR_FALSE); if (rv != SECSuccess) { FATAL_ERROR(ss, SSL_ERROR_INIT_CIPHER_SUITE_FAILURE, internal_error); return SECFailure; @@ -2862,8 +3026,13 @@ tls13_HandleCertificate(sslSocket *ss, PRUint8 *b, PRUint32 length) PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); if (ss->sec.isServer) { - rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE, - wait_client_cert); + if (ss->ssl3.clientCertRequested) { + rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE, + idle_handshake); + } else { + rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE, + wait_client_cert); + } } else { rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE, wait_cert_request, wait_server_cert); @@ -2873,7 +3042,7 @@ tls13_HandleCertificate(sslSocket *ss, PRUint8 *b, PRUint32 length) /* We can ignore any other cleartext from the client. */ if (ss->sec.isServer && IS_DTLS(ss)) { - ssl_CipherSpecReleaseByEpoch(ss, CipherSpecRead, TrafficKeyClearText); + ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read, TrafficKeyClearText); dtls_ReceivedFirstMessageInFlight(ss); } /* Process the context string */ @@ -2881,10 +3050,12 @@ tls13_HandleCertificate(sslSocket *ss, PRUint8 *b, PRUint32 length) if (rv != SECSuccess) return SECFailure; - if (context.len) { - /* The context string MUST be empty */ - FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CERTIFICATE, illegal_parameter); - return SECFailure; + if (ss->ssl3.clientCertRequested) { + PORT_Assert(ss->sec.isServer); + if (SECITEM_CompareItem(&context, &ss->xtnData.certReqContext) != 0) { + FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CERTIFICATE, illegal_parameter); + return SECFailure; + } } rv = ssl3_ConsumeHandshakeVariable(ss, &certList, 3, &b, &length); @@ -3125,6 +3296,25 @@ tls13_DeriveSecretWrap(sslSocket *ss, PK11SymKey *key, return SECSuccess; } +SECStatus +SSLExp_SecretCallback(PRFileDesc *fd, SSLSecretCallback cb, void *arg) +{ + sslSocket *ss = ssl_FindSocket(fd); + if (!ss) { + SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SecretCallback", + SSL_GETPID(), fd)); + return SECFailure; + } + + ssl_Get1stHandshakeLock(ss); + ssl_GetSSL3HandshakeLock(ss); + ss->secretCallback = cb; + ss->secretCallbackArg = arg; + ssl_ReleaseSSL3HandshakeLock(ss); + ssl_Release1stHandshakeLock(ss); + return SECSuccess; +} + /* Derive traffic keys for the next cipher spec in the queue. */ static SECStatus tls13_DeriveTrafficKeys(sslSocket *ss, ssl3CipherSpec *spec, @@ -3251,7 +3441,7 @@ tls13_SetupPendingCipherSpec(sslSocket *ss, ssl3CipherSpec *spec) /* We want to keep read cipher specs around longer because * there are cases where we might get either epoch N or * epoch N+1. */ - if (IS_DTLS(ss) && spec->direction == CipherSpecRead) { + if (IS_DTLS(ss) && spec->direction == ssl_secret_read) { ssl_CipherSpecAddRef(spec); } @@ -3274,7 +3464,7 @@ tls13_SetupPendingCipherSpec(sslSocket *ss, ssl3CipherSpec *spec) /* The record size limit is reduced by one so that the remainder of the * record handling code can use the same checks for all versions. */ if (ssl3_ExtensionNegotiated(ss, ssl_record_size_limit_xtn)) { - spec->recordSizeLimit = ((spec->direction == CipherSpecRead) + spec->recordSizeLimit = ((spec->direction == ssl_secret_read) ? ss->opt.recordSizeLimit : ss->xtnData.recordSizeLimit) - 1; @@ -3310,7 +3500,7 @@ tls13_SetAlertCipherSpec(sslSocket *ss) } rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake, - CipherSpecWrite, PR_FALSE); + ssl_secret_write, PR_FALSE); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; @@ -3325,7 +3515,7 @@ tls13_SetAlertCipherSpec(sslSocket *ss) */ static SECStatus tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch, - CipherSpecDirection direction, PRBool deleteSecret) + SSLSecretDirection direction, PRBool deleteSecret) { TrafficKeyType type; SECStatus rv; @@ -3364,7 +3554,7 @@ tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch, } /* Now that we've set almost everything up, finally cut over. */ - specp = (direction == CipherSpecRead) ? &ss->ssl3.crSpec : &ss->ssl3.cwSpec; + specp = (direction == ssl_secret_read) ? &ss->ssl3.crSpec : &ss->ssl3.cwSpec; ssl_GetSpecWriteLock(ss); ssl_CipherSpecRelease(*specp); /* May delete old cipher. */ *specp = spec; /* Overwrite. */ @@ -3373,11 +3563,6 @@ tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch, SSL_TRC(3, ("%d: TLS13[%d]: %s installed key for epoch=%d (%s) dir=%s", SSL_GETPID(), ss->fd, SSL_ROLE(ss), spec->epoch, spec->phase, SPEC_DIR(spec))); - - if (ss->ssl3.changedCipherSpecFunc) { - ss->ssl3.changedCipherSpecFunc(ss->ssl3.changedCipherSpecArg, - direction == CipherSpecWrite, spec); - } return SECSuccess; loser: @@ -3937,6 +4122,10 @@ tls13_HandleCertificateVerify(sslSocket *ss, PRUint8 *b, PRUint32 length) } } + if (ss->ssl3.clientCertRequested) { + PORT_Assert(ss->sec.isServer); + ss->ssl3.clientCertRequested = PR_FALSE; + } TLS13_SET_HS_STATE(ss, wait_finished); return SECSuccess; @@ -4238,26 +4427,32 @@ tls13_ServerHandleFinished(sslSocket *ss, PRUint8 *b, PRUint32 length) SSL_TRC(3, ("%d: TLS13[%d]: server handle finished handshake", SSL_GETPID(), ss->fd)); - rv = tls13_CommonHandleFinished(ss, ss->ssl3.hs.clientHsTrafficSecret, + rv = tls13_CommonHandleFinished(ss, + ss->firstHsDone ? ss->ssl3.hs.clientTrafficSecret : ss->ssl3.hs.clientHsTrafficSecret, b, length); if (rv != SECSuccess) { return SECFailure; } + if (ss->firstHsDone) { + TLS13_SET_HS_STATE(ss, idle_handshake); + return SECSuccess; + } + if (!tls13_ShouldRequestClientAuth(ss) && (ss->ssl3.hs.zeroRttState != ssl_0rtt_done)) { dtls_ReceivedFirstMessageInFlight(ss); } rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData, - CipherSpecRead, PR_FALSE); + ssl_secret_read, PR_FALSE); if (rv != SECSuccess) { FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); return SECFailure; } if (IS_DTLS(ss)) { - ssl_CipherSpecReleaseByEpoch(ss, CipherSpecRead, TrafficKeyClearText); + ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read, TrafficKeyClearText); /* We need to keep the handshake cipher spec so we can * read re-transmitted client Finished. */ rv = dtls_StartTimer(ss, ss->ssl3.hs.hdTimer, @@ -4361,7 +4556,7 @@ tls13_SendClientSecondFlight(sslSocket *ss, PRBool sendClientCert, } } - rv = tls13_SendFinished(ss, ss->ssl3.hs.clientHsTrafficSecret); + rv = tls13_SendFinished(ss, ss->firstHsDone ? ss->ssl3.hs.clientTrafficSecret : ss->ssl3.hs.clientHsTrafficSecret); if (rv != SECSuccess) { return SECFailure; /* err code was set. */ } @@ -4404,7 +4599,8 @@ tls13_SendClientSecondRound(sslSocket *ss) " certificate authentication is still pending.", SSL_GETPID(), ss->fd)); ss->ssl3.hs.restartTarget = tls13_SendClientSecondRound; - return SECWouldBlock; + PORT_SetError(PR_WOULD_BLOCK_ERROR); + return SECFailure; } rv = tls13_ComputeApplicationSecrets(ss); @@ -4432,14 +4628,14 @@ tls13_SendClientSecondRound(sslSocket *ss) } rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake, - CipherSpecWrite, PR_FALSE); + ssl_secret_write, PR_FALSE); if (rv != SECSuccess) { FATAL_ERROR(ss, SSL_ERROR_INIT_CIPHER_SUITE_FAILURE, internal_error); return SECFailure; } rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData, - CipherSpecRead, PR_FALSE); + ssl_secret_read, PR_FALSE); if (rv != SECSuccess) { FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); return SECFailure; @@ -4457,7 +4653,7 @@ tls13_SendClientSecondRound(sslSocket *ss) return SECFailure; } rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData, - CipherSpecWrite, PR_FALSE); + ssl_secret_write, PR_FALSE); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; @@ -4815,7 +5011,8 @@ static const struct { { ssl_tls13_supported_versions_xtn, _M3(client_hello, server_hello, hello_retry_request) }, { ssl_record_size_limit_xtn, _M2(client_hello, encrypted_extensions) }, - { ssl_tls13_encrypted_sni_xtn, _M2(client_hello, encrypted_extensions) } + { ssl_tls13_encrypted_sni_xtn, _M2(client_hello, encrypted_extensions) }, + { ssl_tls13_post_handshake_auth_xtn, _M1(client_hello) } }; tls13ExtensionStatus @@ -4924,7 +5121,7 @@ tls13_ProtectRecord(sslSocket *ss, const int tagLen = cipher_def->tag_size; SECStatus rv; - PORT_Assert(cwSpec->direction == CipherSpecWrite); + PORT_Assert(cwSpec->direction == ssl_secret_write); SSL_TRC(3, ("%d: TLS13[%d]: spec=%d epoch=%d (%s) protect 0x%0llx len=%u", SSL_GETPID(), ss->fd, cwSpec, cwSpec->epoch, cwSpec->phase, cwSpec->nextSeqNum, contentLen)); @@ -5018,7 +5215,7 @@ tls13_UnprotectRecord(sslSocket *ss, *alert = bad_record_mac; /* Default alert for most issues. */ - PORT_Assert(spec->direction == CipherSpecRead); + PORT_Assert(spec->direction == ssl_secret_read); SSL_TRC(3, ("%d: TLS13[%d]: spec=%d epoch=%d (%s) unprotect 0x%0llx len=%u", SSL_GETPID(), ss->fd, spec, spec->epoch, spec->phase, cText->seqNum, cText->buf->len)); @@ -5175,6 +5372,9 @@ tls13_MaybeDo0RTTHandshake(sslSocket *ss) ss->ssl3.hs.zeroRttState = ssl_0rtt_sent; ss->ssl3.hs.zeroRttSuite = ss->ssl3.hs.cipher_suite; + /* Note: Reset the preliminary info here rather than just add 0-RTT. We are + * only guessing what might happen at this point.*/ + ss->ssl3.hs.preliminaryInfo = ssl_preinfo_0rtt_cipher_suite; SSL_TRC(3, ("%d: TLS13[%d]: in 0-RTT mode", SSL_GETPID(), ss->fd)); @@ -5203,9 +5403,6 @@ tls13_MaybeDo0RTTHandshake(sslSocket *ss) } } - /* Cipher suite already set in tls13_SetupClientHello. */ - ss->ssl3.hs.preliminaryInfo = 0; - rv = tls13_DeriveEarlySecrets(ss); if (rv != SECSuccess) { return SECFailure; @@ -5216,7 +5413,7 @@ tls13_MaybeDo0RTTHandshake(sslSocket *ss) ssl_CipherSpecAddRef(ss->ssl3.cwSpec); rv = tls13_SetCipherSpec(ss, TrafficKeyEarlyApplicationData, - CipherSpecWrite, PR_TRUE); + ssl_secret_write, PR_TRUE); if (rv != SECSuccess) { return SECFailure; } @@ -5279,7 +5476,7 @@ tls13_HandleEndOfEarlyData(sslSocket *ss, PRUint8 *b, PRUint32 length) /* We shouldn't be getting any more early data, and if we do, * it is because of reordering and we drop it. */ if (IS_DTLS(ss)) { - ssl_CipherSpecReleaseByEpoch(ss, CipherSpecRead, + ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read, TrafficKeyEarlyApplicationData); dtls_ReceivedFirstMessageInFlight(ss); } @@ -5292,7 +5489,7 @@ tls13_HandleEndOfEarlyData(sslSocket *ss, PRUint8 *b, PRUint32 length) } rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake, - CipherSpecRead, PR_FALSE); + ssl_secret_read, PR_FALSE); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); return SECFailure; diff --git a/security/nss/lib/ssl/tls13con.h b/security/nss/lib/ssl/tls13con.h index f3b2cb3905a8..f6f52511d623 100644 --- a/security/nss/lib/ssl/tls13con.h +++ b/security/nss/lib/ssl/tls13con.h @@ -131,6 +131,7 @@ SECStatus SSLExp_KeyUpdate(PRFileDesc *fd, PRBool requestUpdate); PRBool tls13_MaybeTls13(sslSocket *ss); SSLAEADCipher tls13_GetAead(const ssl3BulkCipherDef *cipherDef); void tls13_SetSpecRecordVersion(sslSocket *ss, ssl3CipherSpec *spec); +SECStatus SSLExp_SendCertificateRequest(PRFileDesc *fd); /* Use this instead of FATAL_ERROR when no alert shall be sent. */ #define LOG_ERROR(ss, prError) \ diff --git a/security/nss/lib/ssl/tls13exthandle.c b/security/nss/lib/ssl/tls13exthandle.c index 8ed18f69cda9..5ecc2e796565 100644 --- a/security/nss/lib/ssl/tls13exthandle.c +++ b/security/nss/lib/ssl/tls13exthandle.c @@ -915,6 +915,37 @@ tls13_ServerHandleCookieXtn(const sslSocket *ss, TLSExtensionData *xtnData, return SECSuccess; } +SECStatus +tls13_ClientSendPostHandshakeAuthXtn(const sslSocket *ss, + TLSExtensionData *xtnData, + sslBuffer *buf, PRBool *added) +{ + SSL_TRC(3, ("%d: TLS13[%d]: send post_handshake_auth extension", + SSL_GETPID(), ss->fd)); + + *added = ss->opt.enablePostHandshakeAuth; + return SECSuccess; +} + +SECStatus +tls13_ServerHandlePostHandshakeAuthXtn(const sslSocket *ss, + TLSExtensionData *xtnData, + SECItem *data) +{ + SSL_TRC(3, ("%d: TLS13[%d]: handle post_handshake_auth extension", + SSL_GETPID(), ss->fd)); + + if (data->len) { + PORT_SetError(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO); + return SECFailure; + } + + /* Keep track of negotiated extensions. */ + xtnData->negotiated[xtnData->numNegotiated++] = ssl_tls13_post_handshake_auth_xtn; + + return SECSuccess; +} + /* * enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode; * diff --git a/security/nss/lib/ssl/tls13exthandle.h b/security/nss/lib/ssl/tls13exthandle.h index dd64b44ff46c..d36a4f935181 100644 --- a/security/nss/lib/ssl/tls13exthandle.h +++ b/security/nss/lib/ssl/tls13exthandle.h @@ -93,5 +93,11 @@ SECStatus tls13_ClientSendEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData SECStatus tls13_ServerHandleEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData, SECItem *data); SECStatus tls13_ClientCheckEsniXtn(sslSocket *ss); +SECStatus tls13_ClientSendPostHandshakeAuthXtn(const sslSocket *ss, + TLSExtensionData *xtnData, + sslBuffer *buf, PRBool *added); +SECStatus tls13_ServerHandlePostHandshakeAuthXtn(const sslSocket *ss, + TLSExtensionData *xtnData, + SECItem *data); #endif diff --git a/security/nss/nss.gyp b/security/nss/nss.gyp index 3cc3d7b6d7d1..c408058b5038 100644 --- a/security/nss/nss.gyp +++ b/security/nss/nss.gyp @@ -168,6 +168,7 @@ 'cmd/pk11ectest/pk11ectest.gyp:pk11ectest', 'cmd/pk11gcmtest/pk11gcmtest.gyp:pk11gcmtest', 'cmd/pk11mode/pk11mode.gyp:pk11mode', + 'cmd/pk11importtest/pk11importtest.gyp:pk11importtest', 'cmd/pk1sign/pk1sign.gyp:pk1sign', 'cmd/pp/pp.gyp:pp', 'cmd/rsaperf/rsaperf.gyp:rsaperf', diff --git a/security/nss/tests/dbtests/dbtests.sh b/security/nss/tests/dbtests/dbtests.sh index 7b1ee351fded..9dcf738c4c18 100755 --- a/security/nss/tests/dbtests/dbtests.sh +++ b/security/nss/tests/dbtests/dbtests.sh @@ -252,7 +252,15 @@ dbtest_main() else html_passed "Nicknane conflict test-setting nickname conflict was correctly rejected" fi - + # import a token private key and make sure the corresponding public key is + # created + ${BINDIR}/pk11importtest -d ${CONFLICT_DIR} -f ${R_PWFILE} + ret=$? + if [ $ret -ne 0 ]; then + html_failed "Importing Token Private Key does not create the corrresponding Public Key" + else + html_passed "Importing Token Private Key correctly creates the corrresponding Public Key" + fi } ################## main ################################################# diff --git a/security/nss/tests/gtests/gtests.sh b/security/nss/tests/gtests/gtests.sh index 583ecec62c26..1731de0eb471 100755 --- a/security/nss/tests/gtests/gtests.sh +++ b/security/nss/tests/gtests/gtests.sh @@ -23,6 +23,7 @@ gtest_init() { cd "$(dirname "$1")" + SOURCE_DIR="$PWD"/../.. if [ -z "${INIT_SOURCED}" -o "${INIT_SOURCED}" != "TRUE" ]; then cd ../common . ./init.sh @@ -33,6 +34,7 @@ gtest_init() if [ -z "${CLEANUP}" ] ; then # if nobody else is responsible for CLEANUP="${SCRIPTNAME}" # cleaning this script will do it fi + } ########################## gtest_start ############################# @@ -42,7 +44,7 @@ gtest_start() { echo "gtests: ${GTESTS}" for i in ${GTESTS}; do - if [ ! -f ${BINDIR}/$i ]; then + if [ ! -f "${BINDIR}/$i" ]; then html_unknown "Skipping $i (not built)" continue fi @@ -50,20 +52,22 @@ gtest_start() html_head "$i" if [ ! -d "$GTESTDIR" ]; then mkdir -p "$GTESTDIR" + echo "${BINDIR}/certutil" -N -d "$GTESTDIR" --empty-password 2>&1 + "${BINDIR}/certutil" -N -d "$GTESTDIR" --empty-password 2>&1 fi cd "$GTESTDIR" GTESTREPORT="$GTESTDIR/report.xml" PARSED_REPORT="$GTESTDIR/report.parsed" echo "executing $i" - ${BINDIR}/$i "${SOURCE_DIR}/gtests/freebl_gtest/kat/Hash_DRBG.rsp" \ - -d "$GTESTDIR" --gtest_output=xml:"${GTESTREPORT}" \ - --gtest_filter="${GTESTFILTER-*}" + "${BINDIR}/$i" "${SOURCE_DIR}/gtests/freebl_gtest/kat/Hash_DRBG.rsp" \ + -d "$GTESTDIR" -w --gtest_output=xml:"${GTESTREPORT}" \ + --gtest_filter="${GTESTFILTER:-*}" html_msg $? 0 "$i run successfully" echo "test output dir: ${GTESTREPORT}" echo "executing sed to parse the xml report" - sed -f ${COMMON}/parsegtestreport.sed "${GTESTREPORT}" > "${PARSED_REPORT}" + sed -f "${COMMON}/parsegtestreport.sed" "$GTESTREPORT" > "$PARSED_REPORT" echo "processing the parsed report" - cat "${PARSED_REPORT}" | while read result name; do + cat "$PARSED_REPORT" | while read result name; do if [ "$result" = "notrun" ]; then echo "$name" SKIPPED elif [ "$result" = "run" ]; then @@ -78,13 +82,12 @@ gtest_start() gtest_cleanup() { html "
" - cd ${QADIR} + cd "${QADIR}" . common/cleanup.sh } ################## main ################################################# -GTESTS="prng_gtest certhigh_gtest certdb_gtest der_gtest pk11_gtest util_gtest freebl_gtest softoken_gtest sysinit_gtest blake2b_gtest" -SOURCE_DIR="$PWD"/../.. -gtest_init $0 +GTESTS="${GTESTS:-prng_gtest certhigh_gtest certdb_gtest der_gtest pk11_gtest util_gtest freebl_gtest softoken_gtest sysinit_gtest blake2b_gtest}" +gtest_init "$0" gtest_start gtest_cleanup diff --git a/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth.html b/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth.html index 7248f3a679a6..1d1d572507de 100644 --- a/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth.html +++ b/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth.html @@ -43,25 +43,25 @@ let prompterParent = runInParent(() => { let prompter2 = promptFac.getPrompt(chromeWin, Ci.nsIAuthPrompt2); let channels = {}; - channels.channel1 = Services.io.newChannel2("http://example.com", - null, - null, - null, // aLoadingNode - Services. - scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - Ci.nsIContentPolicy.TYPE_OTHER); + channels.channel1 = Services.io.newChannel("http://example.com", + null, + null, + null, // aLoadingNode + Services. + scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); - channels.channel2 = Services.io.newChannel2("http://example2.com", - null, - null, - null, // aLoadingNode - Services. - scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - Ci.nsIContentPolicy.TYPE_OTHER); + channels.channel2 = Services.io.newChannel("http://example2.com", + null, + null, + null, // aLoadingNode + Services. + scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); addMessageListener("proxyPrompter", function onMessage(msg) { let args = [...msg.args]; diff --git a/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html b/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html index 98282c62069e..fbdcf40f5290 100644 --- a/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html +++ b/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html @@ -103,14 +103,14 @@ var resolveCallback = SpecialPowers.wrapCallbackObject({ // The proxyChannel needs to move to at least on-modify-request to // have valid ProxyInfo, but we use OnStartRequest during startup() // for simplicity. - proxyChannel = SpecialPowers.Services.io.newChannel2(proxiedHost, - null, - null, - null, // aLoadingNode - systemPrincipal, - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - Ci.nsIContentPolicy.TYPE_OTHER); + proxyChannel = SpecialPowers.Services.io.newChannel(proxiedHost, + null, + null, + null, // aLoadingNode + systemPrincipal, + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); proxyChannel.asyncOpen(SpecialPowers.wrapCallbackObject(new proxyChannelListener())); }, }); @@ -119,14 +119,14 @@ function startup() { // Need to allow for arbitrary network servers defined in PAC instead of a hardcoded moz-proxy. var pps = SpecialPowers.Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(); - var channel = SpecialPowers.Services.io.newChannel2("http://example.com", - null, - null, - null, // aLoadingNode - systemPrincipal, - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - Ci.nsIContentPolicy.TYPE_OTHER); + var channel = SpecialPowers.Services.io.newChannel("http://example.com", + null, + null, + null, // aLoadingNode + systemPrincipal, + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); pps.asyncResolve(channel, 0, resolveCallback); } diff --git a/toolkit/components/reputationservice/ApplicationReputation.cpp b/toolkit/components/reputationservice/ApplicationReputation.cpp index b47f8491b331..b2bcfed44583 100644 --- a/toolkit/components/reputationservice/ApplicationReputation.cpp +++ b/toolkit/components/reputationservice/ApplicationReputation.cpp @@ -1584,12 +1584,12 @@ nsresult PendingLookup::SendRemoteQueryInternal(Reason& aReason) { // Set up the channel to transmit the request to the service. nsCOMPtr ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); - rv = ios->NewChannel2(serviceUrl, nullptr, nullptr, - nullptr, // aLoadingNode - nsContentUtils::GetSystemPrincipal(), - nullptr, // aTriggeringPrincipal - nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - nsIContentPolicy::TYPE_OTHER, getter_AddRefs(mChannel)); + rv = ios->NewChannel(serviceUrl, nullptr, nullptr, + nullptr, // aLoadingNode + nsContentUtils::GetSystemPrincipal(), + nullptr, // aTriggeringPrincipal + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, getter_AddRefs(mChannel)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr loadInfo = mChannel->LoadInfo(); diff --git a/toolkit/components/search/SearchService.jsm b/toolkit/components/search/SearchService.jsm index cf1ba32cd0a3..aef3d7a35c07 100644 --- a/toolkit/components/search/SearchService.jsm +++ b/toolkit/components/search/SearchService.jsm @@ -735,12 +735,12 @@ function makeURI(aURLSpec, aCharset) { function makeChannel(url) { try { let uri = typeof url == "string" ? Services.io.newURI(url) : url; - return Services.io.newChannelFromURI2(uri, - null, /* loadingNode */ - Services.scriptSecurityManager.getSystemPrincipal(), - null, /* triggeringPrincipal */ - Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, - Ci.nsIContentPolicy.TYPE_OTHER); + return Services.io.newChannelFromURI(uri, + null, /* loadingNode */ + Services.scriptSecurityManager.getSystemPrincipal(), + null, /* triggeringPrincipal */ + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); } catch (ex) { } return null;