Merge mozilla-central to autoland. a=merge CLOSED TREE

This commit is contained in:
Gurzau Raul 2019-02-21 23:56:21 +02:00
commit dd9cb929e6
100 changed files with 4076 additions and 1886 deletions

View file

@ -778,7 +778,7 @@ window._gBrowser = {
try { try {
// Use the passed in resolvedURI if we have one // Use the passed in resolvedURI if we have one
const resolvedURI = aResolvedURI || Services.io.newChannelFromURI2( const resolvedURI = aResolvedURI || Services.io.newChannelFromURI(
aURI, aURI,
null, // loadingNode null, // loadingNode
Services.scriptSecurityManager.getSystemPrincipal(), // loadingPrincipal Services.scriptSecurityManager.getSystemPrincipal(), // loadingPrincipal

View file

@ -1591,7 +1591,7 @@ var SessionStoreInternal = {
onQuitApplicationGranted: function ssi_onQuitApplicationGranted(syncShutdown = false) { onQuitApplicationGranted: function ssi_onQuitApplicationGranted(syncShutdown = false) {
// Collect an initial snapshot of window data before we do the flush. // Collect an initial snapshot of window data before we do the flush.
let index = 0; let index = 0;
for (let window of this._browserWindows) { for (let window of this._orderedBrowserWindows) {
this._collectWindowData(window); this._collectWindowData(window);
this._windows[window.__SSi].zIndex = ++index; this._windows[window.__SSi].zIndex = ++index;
} }
@ -3416,7 +3416,7 @@ var SessionStoreInternal = {
if (RunState.isRunning) { if (RunState.isRunning) {
// update the data for all windows with activities since the last save operation. // update the data for all windows with activities since the last save operation.
let index = 0; 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 if (!this._isWindowLoaded(window)) // window data is still in _statesToRestore
continue; continue;
if (aUpdateAll || DirtyWindows.has(window) || window == activeWindow) { 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.) * (Might miss the most recent one.)
* This list is in focus order, but may include minimized windows
* before non-minimized windows.
*/ */
_browserWindows: { _browserWindows: {
* [Symbol.iterator]() { * [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 most recent window
* @returns Window reference * @returns Window reference

View file

@ -19,7 +19,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
// Constants // Constants
const TAB_EVENTS = ["TabBrowserInserted", "TabSelect"]; const TAB_EVENTS = ["TabBrowserInserted", "TabSelect"];
const WINDOW_EVENTS = ["activate", "sizemodechange", "unload"]; const WINDOW_EVENTS = ["activate", "unload"];
const DEBUG = false; const DEBUG = false;
// Variables // Variables
@ -64,9 +64,6 @@ function _handleEvent(event) {
case "activate": case "activate":
WindowHelper.onActivate(event.target); WindowHelper.onActivate(event.target);
break; break;
case "sizemodechange":
WindowHelper.onSizemodeChange(event.target);
break;
case "unload": case "unload":
WindowHelper.removeWindow(event.currentTarget); WindowHelper.removeWindow(event.currentTarget);
break; break;
@ -144,14 +141,6 @@ var WindowHelper = {
_updateCurrentContentOuterWindowID(window.gBrowser.selectedBrowser); _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 = { this.BrowserWindowTracker = {

View file

@ -90,7 +90,7 @@ class FaviconLoad {
constructor(iconInfo) { constructor(iconInfo) {
this.icon = iconInfo; this.icon = iconInfo;
this.channel = Services.io.newChannelFromURI2( this.channel = Services.io.newChannelFromURI(
iconInfo.iconUri, iconInfo.iconUri,
iconInfo.node, iconInfo.node,
iconInfo.node.nodePrincipal, iconInfo.node.nodePrincipal,

View file

@ -110,16 +110,5 @@ add_task(async function test_orderedWindows() {
let expected = [1, 6, 4, 9, 8, 7, 5, 3, 2, 0]; let expected = [1, 6, 4, 9, 8, 7, 5, 3, 2, 0];
Assert.deepEqual(expected, ordered2.map(w => windows.indexOf(w)), Assert.deepEqual(expected, ordered2.map(w => windows.indexOf(w)),
"After shuffle of focused windows, the order should've changed."); "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.");
}); });
}); });

View file

@ -28,12 +28,7 @@ loader.lazyRequireGetter(this, "InspectorSearch", "devtools/client/inspector/ins
loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/inspector/toolsidebar", true); loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/inspector/toolsidebar", true);
loader.lazyRequireGetter(this, "MarkupView", "devtools/client/inspector/markup/markup"); loader.lazyRequireGetter(this, "MarkupView", "devtools/client/inspector/markup/markup");
loader.lazyRequireGetter(this, "HighlightersOverlay", "devtools/client/inspector/shared/highlighters-overlay"); 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, "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.lazyRequireGetter(this, "saveScreenshot", "devtools/shared/screenshot/save");
loader.lazyImporter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm"); 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 {LocalizationHelper, localizeMarkup} = require("devtools/shared/l10n");
const INSPECTOR_L10N = const INSPECTOR_L10N =
new LocalizationHelper("devtools/client/locales/inspector.properties"); new LocalizationHelper("devtools/client/locales/inspector.properties");
loader.lazyGetter(this, "TOOLBOX_L10N", function() {
return new LocalizationHelper("devtools/client/locales/toolbox.properties");
});
// Sidebar dimensions // Sidebar dimensions
const INITIAL_SIDEBAR_SIZE = 350; const INITIAL_SIDEBAR_SIZE = 350;
@ -125,12 +117,9 @@ function Inspector(toolbox) {
// telemetry counts in the Grid Inspector are not double counted on reload. // telemetry counts in the Grid Inspector are not double counted on reload.
this.previousURL = this.target.url; this.previousURL = this.target.url;
this.nodeMenuTriggerInfo = null;
this._clearSearchResultsLabel = this._clearSearchResultsLabel.bind(this); this._clearSearchResultsLabel = this._clearSearchResultsLabel.bind(this);
this._handleRejectionIfNotDestroyed = this._handleRejectionIfNotDestroyed.bind(this); this._handleRejectionIfNotDestroyed = this._handleRejectionIfNotDestroyed.bind(this);
this._onBeforeNavigate = this._onBeforeNavigate.bind(this); this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
this._onContextMenu = this._onContextMenu.bind(this);
this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this); this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this); this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this);
@ -1402,8 +1391,6 @@ Inspector.prototype = {
if (this._markupFrame) { if (this._markupFrame) {
this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true); this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
this._markupFrame.contentWindow.removeEventListener("contextmenu",
this._onContextMenu);
} }
if (this._search) { if (this._search) {
@ -1449,449 +1436,6 @@ Inspector.prototype = {
return this._panelDestroyer; 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() { _initMarkup: function() {
if (!this._markupFrame) { if (!this._markupFrame) {
this._markupFrame = this.panelDoc.createElement("iframe"); this._markupFrame = this.panelDoc.createElement("iframe");
@ -1913,7 +1457,6 @@ Inspector.prototype = {
_onMarkupFrameLoad: function() { _onMarkupFrameLoad: function() {
this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true); this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
this._markupFrame.contentWindow.addEventListener("contextmenu", this._onContextMenu);
this._markupFrame.contentWindow.focus(); this._markupFrame.contentWindow.focus();
this._markupBox.style.visibility = "visible"; this._markupBox.style.visibility = "visible";
this.markup = new MarkupView(this, this._markupFrame, this._toolbox.win); this.markup = new MarkupView(this, this._markupFrame, this._toolbox.win);
@ -2028,241 +1571,6 @@ Inspector.prototype = {
return promise.resolve(); 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("<!--" + comment + "-->");
});
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. * Initiate screenshot command on selected node.
*/ */
@ -2285,160 +1593,6 @@ Inspector.prototype = {
await saveScreenshot(this.panelWin, args, screenshot); 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 * Returns an object containing the shared handler functions used in the box
* model and grid React components. * model and grid React components.
@ -2464,18 +1618,6 @@ Inspector.prototype = {
toolbox.highlighter.highlight(nodeFront, options); 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) { async inspectNodeActor(nodeActor, inspectFromAnnotation) {
const nodeFront = await this.walker.gripToNodeFront({ actor: nodeActor }); const nodeFront = await this.walker.gripToNodeFront({ actor: nodeActor });
if (!nodeFront) { if (!nodeFront) {

View file

@ -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;

View file

@ -21,9 +21,14 @@ const MarkupReadOnlyContainer = require("devtools/client/inspector/markup/views/
const MarkupTextContainer = require("devtools/client/inspector/markup/views/text-container"); const MarkupTextContainer = require("devtools/client/inspector/markup/views/text-container");
const RootContainer = require("devtools/client/inspector/markup/views/root-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, "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, "HTMLTooltip", "devtools/client/shared/widgets/tooltip/HTMLTooltip", true);
loader.lazyRequireGetter(this, "UndoStack", "devtools/client/shared/undo", true); loader.lazyRequireGetter(this, "UndoStack", "devtools/client/shared/undo", true);
loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
const INSPECTOR_L10N = const INSPECTOR_L10N =
new LocalizationHelper("devtools/client/locales/inspector.properties"); new LocalizationHelper("devtools/client/locales/inspector.properties");
@ -98,6 +103,7 @@ function MarkupView(inspector, frame, controllerWindow) {
this._isImagePreviewTarget = this._isImagePreviewTarget.bind(this); this._isImagePreviewTarget = this._isImagePreviewTarget.bind(this);
this._mutationObserver = this._mutationObserver.bind(this); this._mutationObserver = this._mutationObserver.bind(this);
this._onBlur = this._onBlur.bind(this); this._onBlur = this._onBlur.bind(this);
this._onContextMenu = this._onContextMenu.bind(this);
this._onCopy = this._onCopy.bind(this); this._onCopy = this._onCopy.bind(this);
this._onCollapseAttributesPrefChange = this._onCollapseAttributesPrefChange.bind(this); this._onCollapseAttributesPrefChange = this._onCollapseAttributesPrefChange.bind(this);
this._onWalkerNodeStatesChanged = this._onWalkerNodeStatesChanged.bind(this); this._onWalkerNodeStatesChanged = this._onWalkerNodeStatesChanged.bind(this);
@ -113,6 +119,7 @@ function MarkupView(inspector, frame, controllerWindow) {
// Listening to various events. // Listening to various events.
this._elt.addEventListener("blur", this._onBlur, true); this._elt.addEventListener("blur", this._onBlur, true);
this._elt.addEventListener("click", this._onMouseClick); this._elt.addEventListener("click", this._onMouseClick);
this._elt.addEventListener("contextmenu", this._onContextMenu);
this._elt.addEventListener("mousemove", this._onMouseMove); this._elt.addEventListener("mousemove", this._onMouseMove);
this._elt.addEventListener("mouseout", this._onMouseOut); this._elt.addEventListener("mouseout", this._onMouseOut);
this._frame.addEventListener("focus", this._onFocus); this._frame.addEventListener("focus", this._onFocus);
@ -156,6 +163,14 @@ MarkupView.prototype = {
_selectedContainer: null, _selectedContainer: null,
get contextMenu() {
if (!this._contextMenu) {
this._contextMenu = new MarkupContextMenu(this);
}
return this._contextMenu;
},
get eventDetailsTooltip() { get eventDetailsTooltip() {
if (!this._eventDetailsTooltip) { if (!this._eventDetailsTooltip) {
// This tooltip will be attached to the toolbox document. // 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. * 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 * 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; const selection = this.inspector.selection;
if (selection.isNode()) { if (selection.isNode()) {
this.inspector.copyOuterHTML(); this.copyOuterHTML();
} }
evt.stopPropagation(); evt.stopPropagation();
evt.preventDefault(); 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("<!--" + comment + "-->");
});
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. * Register all key shortcuts.
*/ */
@ -820,8 +900,7 @@ MarkupView.prototype = {
break; break;
} }
case "markupView.scrollInto.key": { case "markupView.scrollInto.key": {
const selection = this._selectedContainer.node; this.scrollNodeIntoView();
this.inspector.scrollNodeIntoView(selection);
break; break;
} }
// Generic keys // 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. * Delete a node from the DOM.
* This is an undoable action. * This is an undoable action.
@ -970,7 +1061,7 @@ MarkupView.prototype = {
* If set to true, focus the previous sibling, otherwise the next one. * If set to true, focus the previous sibling, otherwise the next one.
*/ */
deleteNode: function(node, moveBackward) { deleteNode: function(node, moveBackward) {
if (!this.inspector.isDeletable(node)) { if (!this.isDeletable(node)) {
return; return;
} }
@ -1017,6 +1108,17 @@ MarkupView.prototype = {
}).catch(console.error); }).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. * 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("blur", this._onBlur, true);
this._elt.removeEventListener("click", this._onMouseClick); this._elt.removeEventListener("click", this._onMouseClick);
this._elt.removeEventListener("contextmenu", this._onContextMenu);
this._elt.removeEventListener("mousemove", this._onMouseMove); this._elt.removeEventListener("mousemove", this._onMouseMove);
this._elt.removeEventListener("mouseout", this._onMouseOut); this._elt.removeEventListener("mouseout", this._onMouseOut);
this._frame.removeEventListener("focus", this._onFocus); this._frame.removeEventListener("focus", this._onFocus);

View file

@ -9,6 +9,7 @@ DIRS += [
] ]
DevToolsModules( DevToolsModules(
'markup-context-menu.js',
'markup.js', 'markup.js',
'utils.js', 'utils.js',
) )

View file

@ -23,7 +23,7 @@ add_task(async function() {
info("Follow the link and wait for the new tab to open"); info("Follow the link and wait for the new tab to open");
const onTabOpened = once(gBrowser.tabContainer, "TabOpen"); const onTabOpened = once(gBrowser.tabContainer, "TabOpen");
inspector.onFollowLink(); inspector.markup.contextMenu._onFollowLink();
const {target: tab} = await onTabOpened; const {target: tab} = await onTabOpened;
await BrowserTestUtils.browserLoaded(tab.linkedBrowser); 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"); info("Follow the link and wait for the new node to be selected");
const onSelection = inspector.selection.once("new-node-front"); const onSelection = inspector.selection.once("new-node-front");
inspector.onFollowLink(); inspector.markup.contextMenu._onFollowLink();
await onSelection; await onSelection;
ok(true, "A new node was selected"); 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"); info("Try to follow the link and check that no new node were selected");
const onFailed = inspector.once("idref-attribute-link-failed"); const onFailed = inspector.markup.once("idref-attribute-link-failed");
inspector.onFollowLink(); inspector.markup.contextMenu._onFollowLink();
await onFailed; await onFailed;
ok(true, "The node selection failed"); ok(true, "The node selection failed");

View file

@ -23,7 +23,7 @@ add_task(async function() {
info("Follow the link and wait for the style-editor to open"); info("Follow the link and wait for the style-editor to open");
const onStyleEditorReady = toolbox.once("styleeditor-ready"); const onStyleEditorReady = toolbox.once("styleeditor-ready");
inspector.onFollowLink(); inspector.markup.contextMenu._onFollowLink();
await onStyleEditorReady; await onStyleEditorReady;
// No real need to test that the editor opened on the right file here as this // 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"); info("Follow the link and wait for the debugger to open");
const onDebuggerReady = toolbox.once("jsdebugger-ready"); const onDebuggerReady = toolbox.once("jsdebugger-ready");
inspector.onFollowLink(); inspector.markup.contextMenu._onFollowLink();
await onDebuggerReady; await onDebuggerReady;
// No real need to test that the debugger opened on the right file here as // No real need to test that the debugger opened on the right file here as

View file

@ -99,7 +99,7 @@ async function followLinkWaitForNewNode(linkEl, isMetaClick, inspector) {
} }
async function followLinkNoNewNode(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); performMouseDown(linkEl, isMetaClick);
await onFailed; await onFailed;

View file

@ -45,9 +45,12 @@ add_task(async function() {
await selectNode(divContainer.node, inspector); await selectNode(divContainer.node, inspector);
info("Check the copied values for the various copy*Path helpers"); info("Check the copied values for the various copy*Path helpers");
await waitForClipboardPromise(() => inspector.copyXPath(), '//*[@id="el1"]'); await waitForClipboardPromise(() => inspector.markup.contextMenu._copyXPath(),
await waitForClipboardPromise(() => inspector.copyCssPath(), "div#el1"); '//*[@id="el1"]');
await waitForClipboardPromise(() => inspector.copyUniqueSelector(), "#el1"); await waitForClipboardPromise(() => inspector.markup.contextMenu._copyCssPath(),
"div#el1");
await waitForClipboardPromise(() => inspector.markup.contextMenu._copyUniqueSelector(),
"#el1");
info("Expand the div"); info("Expand the div");
await expandContainer(inspector, divContainer); await expandContainer(inspector, divContainer);
@ -57,8 +60,10 @@ add_task(async function() {
await selectNode(spanContainer.node, inspector); await selectNode(spanContainer.node, inspector);
info("Check the copied values for the various copy*Path helpers"); info("Check the copied values for the various copy*Path helpers");
await waitForClipboardPromise(() => inspector.copyXPath(), "/div/span[3]"); await waitForClipboardPromise(() => inspector.markup.contextMenu._copyXPath(),
await waitForClipboardPromise(() => inspector.copyCssPath(), "div#el1 span"); "/div/span[3]");
await waitForClipboardPromise(() => inspector.copyUniqueSelector(), await waitForClipboardPromise(() => inspector.markup.contextMenu._copyCssPath(),
"div#el1 span");
await waitForClipboardPromise(() => inspector.markup.contextMenu._copyUniqueSelector(),
"#el1 > span:nth-child(3)"); "#el1 > span:nth-child(3)");
}); });

View file

@ -564,7 +564,7 @@ MarkupContainer.prototype = {
const type = target.dataset.type; const type = target.dataset.type;
// Make container tabbable descendants not tabbable (by default). // Make container tabbable descendants not tabbable (by default).
this.canFocus = false; this.canFocus = false;
this.markup.inspector.followAttributeLink(type, link); this.markup.followAttributeLink(type, link);
return; return;
} }

View file

@ -11,6 +11,7 @@ const promise = require("promise");
loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true); loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
loader.lazyRequireGetter(this, "getCSSLexer", "devtools/shared/css/lexer", true); loader.lazyRequireGetter(this, "getCSSLexer", "devtools/shared/css/lexer", true);
loader.lazyRequireGetter(this, "parseDeclarations", "devtools/shared/css/parsing-utils", 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"; 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. * Create a child element with a set of attributes.
* *
@ -109,6 +125,22 @@ function createChild(parent, tagName, attributes = {}) {
return elt; 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 * 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. * getElements() function in our devtools-reps component for a ElementNode.
@ -196,7 +228,9 @@ function translateNodeFrontToGrip(nodeFront) {
exports.advanceValidate = advanceValidate; exports.advanceValidate = advanceValidate;
exports.appendText = appendText; exports.appendText = appendText;
exports.blurOnMultipleProperties = blurOnMultipleProperties; exports.blurOnMultipleProperties = blurOnMultipleProperties;
exports.copyLongString = copyLongString;
exports.createChild = createChild; exports.createChild = createChild;
exports.getLongString = getLongString;
exports.getSelectorFromGrip = getSelectorFromGrip; exports.getSelectorFromGrip = getSelectorFromGrip;
exports.promiseWarn = promiseWarn; exports.promiseWarn = promiseWarn;
exports.translateNodeFrontToGrip = translateNodeFrontToGrip; exports.translateNodeFrontToGrip = translateNodeFrontToGrip;

View file

@ -36,7 +36,7 @@ add_task(async function() {
const copyAttributeValue = getMenuItem("node-menu-copy-attribute"); const copyAttributeValue = getMenuItem("node-menu-copy-attribute");
info("Triggering 'Copy Attribute Value' and waiting for clipboard to copy the value"); info("Triggering 'Copy Attribute Value' and waiting for clipboard to copy the value");
inspector.nodeMenuTriggerInfo = { inspector.markup.contextMenu.nodeMenuTriggerInfo = {
type: "attribute", type: "attribute",
name: "data-edit", name: "data-edit",
value: "the", value: "the",
@ -53,7 +53,7 @@ add_task(async function() {
"23456789012345678901234567890123456789012345678901234567890123456789012" + "23456789012345678901234567890123456789012345678901234567890123456789012" +
"34567890123456789012345678901234567890123456789012345678901234567890123"; "34567890123456789012345678901234567890123456789012345678901234567890123";
inspector.nodeMenuTriggerInfo = { inspector.markup.contextMenu.nodeMenuTriggerInfo = {
type: "attribute", type: "attribute",
name: "data-edit", name: "data-edit",
value: longAttribute, value: longAttribute,
@ -67,7 +67,7 @@ add_task(async function() {
const editAttribute = getMenuItem("node-menu-edit-attribute"); const editAttribute = getMenuItem("node-menu-edit-attribute");
info("Triggering 'Edit Attribute' and waiting for mutation to occur"); info("Triggering 'Edit Attribute' and waiting for mutation to occur");
inspector.nodeMenuTriggerInfo = { inspector.markup.contextMenu.nodeMenuTriggerInfo = {
type: "attribute", type: "attribute",
name: "data-edit", name: "data-edit",
}; };
@ -87,7 +87,7 @@ add_task(async function() {
const removeAttribute = getMenuItem("node-menu-remove-attribute"); const removeAttribute = getMenuItem("node-menu-remove-attribute");
info("Triggering 'Remove Attribute' and waiting for mutation to occur"); info("Triggering 'Remove Attribute' and waiting for mutation to occur");
inspector.nodeMenuTriggerInfo = { inspector.markup.contextMenu.nodeMenuTriggerInfo = {
type: "attribute", type: "attribute",
name: "data-remove", name: "data-remove",
}; };

View file

@ -82,7 +82,7 @@ add_task(async function() {
async function checkTextBox(textBox, toolbox) { async function checkTextBox(textBox, toolbox) {
let textboxContextMenu = toolbox.doc.getElementById("toolbox-menu"); 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"); info("Simulating context click on the textbox and expecting the menu to open");
const onContextMenu = toolbox.once("menu-open"); const onContextMenu = toolbox.once("menu-open");

View file

@ -660,6 +660,6 @@ function openStyleContextMenuAndGetAllItems(view, target) {
* @return An array of MenuItems * @return An array of MenuItems
*/ */
function openContextMenuAndGetAllItems(inspector, options) { function openContextMenuAndGetAllItems(inspector, options) {
const menu = inspector._openMenu(options); const menu = inspector.markup.contextMenu._openMenu(options);
return buildContextMenuItems(menu); return buildContextMenuItems(menu);
} }

View file

@ -31,14 +31,14 @@ function loadFileContent(aFile, aCharset) {
var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1'] var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
.getService(SpecialPowers.Ci.nsIIOService); .getService(SpecialPowers.Ci.nsIIOService);
var chann = ios.newChannel2(aFile, var chann = ios.newChannel(aFile,
aCharset, aCharset,
baseUri, baseUri,
null, // aLoadingNode null, // aLoadingNode
SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal null, // aTriggeringPrincipal
SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER);
var cis = SpecialPowers.Ci.nsIConverterInputStream; var cis = SpecialPowers.Ci.nsIConverterInputStream;

View file

@ -30,14 +30,14 @@ function loadFileContent(aFile, aCharset) {
var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1'] var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
.getService(SpecialPowers.Ci.nsIIOService); .getService(SpecialPowers.Ci.nsIIOService);
var chann = ios.newChannel2(aFile, var chann = ios.newChannel(aFile,
aCharset, aCharset,
baseUri, baseUri,
null, // aLoadingNode null, // aLoadingNode
SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal null, // aTriggeringPrincipal
SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER);
var cis = SpecialPowers.Ci.nsIConverterInputStream; var cis = SpecialPowers.Ci.nsIConverterInputStream;

View file

@ -31,14 +31,14 @@ function loadFileContent(aFile, aCharset) {
var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1'] var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
.getService(SpecialPowers.Ci.nsIIOService); .getService(SpecialPowers.Ci.nsIIOService);
var chann = ios.newChannel2(aFile, var chann = ios.newChannel(aFile,
aCharset, aCharset,
baseUri, baseUri,
null, // aLoadingNode null, // aLoadingNode
SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal null, // aTriggeringPrincipal
SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER);
var cis = SpecialPowers.Ci.nsIConverterInputStream; var cis = SpecialPowers.Ci.nsIConverterInputStream;

View file

@ -30,14 +30,14 @@ function loadFileContent(aFile, aCharset) {
var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1'] var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
.getService(SpecialPowers.Ci.nsIIOService); .getService(SpecialPowers.Ci.nsIIOService);
var chann = ios.newChannel2(aFile, var chann = ios.newChannel(aFile,
aCharset, aCharset,
baseUri, baseUri,
null, // aLoadingNode null, // aLoadingNode
SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal null, // aTriggeringPrincipal
SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER);
var cis = SpecialPowers.Ci.nsIConverterInputStream; var cis = SpecialPowers.Ci.nsIConverterInputStream;

View file

@ -29,14 +29,14 @@ function loadFileContent(aFile, aCharset) {
var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1'] var ios = SpecialPowers.Cc['@mozilla.org/network/io-service;1']
.getService(SpecialPowers.Ci.nsIIOService); .getService(SpecialPowers.Ci.nsIIOService);
var chann = ios.newChannel2(aFile, var chann = ios.newChannel(aFile,
aCharset, aCharset,
baseUri, baseUri,
null, // aLoadingNode null, // aLoadingNode
SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(), SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal null, // aTriggeringPrincipal
SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, SpecialPowers.Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER); SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER);
var cis = SpecialPowers.Ci.nsIConverterInputStream; var cis = SpecialPowers.Ci.nsIConverterInputStream;

View file

@ -3422,6 +3422,14 @@ static MathSpace ExtractMathSpace(MDefinition* ins) {
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unknown TruncateKind"); 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 // Extract a linear sum from ins, if possible (otherwise giving the
// sum 'ins + 0'). // sum 'ins + 0').
SimpleLinearSum jit::ExtractLinearSum(MDefinition* ins, MathSpace space) { SimpleLinearSum jit::ExtractLinearSum(MDefinition* ins, MathSpace space) {
@ -3471,7 +3479,8 @@ SimpleLinearSum jit::ExtractLinearSum(MDefinition* ins, MathSpace space) {
int32_t constant; int32_t constant;
if (space == MathSpace::Modulo) { if (space == MathSpace::Modulo) {
constant = uint32_t(lsum.constant) + uint32_t(rsum.constant); 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(ins, 0);
} }
return SimpleLinearSum(lsum.term ? lsum.term : rsum.term, constant); return SimpleLinearSum(lsum.term ? lsum.term : rsum.term, constant);
@ -3483,7 +3492,8 @@ SimpleLinearSum jit::ExtractLinearSum(MDefinition* ins, MathSpace space) {
int32_t constant; int32_t constant;
if (space == MathSpace::Modulo) { if (space == MathSpace::Modulo) {
constant = uint32_t(lsum.constant) - uint32_t(rsum.constant); 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(ins, 0);
} }
return SimpleLinearSum(lsum.term, constant); return SimpleLinearSum(lsum.term, constant);

View file

@ -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 // -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. // introduce new behavior. can't follow redirects on connect anyway.
nsCOMPtr<nsIChannel> localChannel; nsCOMPtr<nsIChannel> localChannel;
rv = ioService->NewChannelFromURIWithProxyFlags2( rv = ioService->NewChannelFromURIWithProxyFlags(
uri, nullptr, uri, nullptr,
// Proxy flags are overridden by SetConnectOnly() // Proxy flags are overridden by SetConnectOnly()
0, aLoadInfo->LoadingNode(), aLoadInfo->LoadingPrincipal(), 0, aLoadInfo->LoadingNode(), aLoadInfo->LoadingPrincipal(),

View file

@ -301,12 +301,12 @@ var NetUtil = {
contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER; contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
} }
return Services.io.newChannelFromURI2(uri, return Services.io.newChannelFromURI(uri,
loadingNode || null, loadingNode || null,
loadingPrincipal || null, loadingPrincipal || null,
triggeringPrincipal || null, triggeringPrincipal || null,
securityFlags, securityFlags,
contentPolicyType); contentPolicyType);
}, },
/** /**

View file

@ -101,12 +101,12 @@ interface nsIIOService : nsISupports
* Keep in mind that URIs coming from a webpage should *never* use the * Keep in mind that URIs coming from a webpage should *never* use the
* systemPrincipal as the loadingPrincipal. * systemPrincipal as the loadingPrincipal.
*/ */
nsIChannel newChannelFromURI2(in nsIURI aURI, nsIChannel newChannelFromURI(in nsIURI aURI,
in Node aLoadingNode, in Node aLoadingNode,
in nsIPrincipal aLoadingPrincipal, in nsIPrincipal aLoadingPrincipal,
in nsIPrincipal aTriggeringPrincipal, in nsIPrincipal aTriggeringPrincipal,
in unsigned long aSecurityFlags, in unsigned long aSecurityFlags,
in unsigned long aContentPolicyType); in unsigned long aContentPolicyType);
[noscript, nostdcall, notxpcom] [noscript, nostdcall, notxpcom]
nsresult NewChannelFromURIWithClientAndController(in nsIURI aURI, nsresult NewChannelFromURIWithClientAndController(in nsIURI aURI,
@ -120,22 +120,22 @@ interface nsIIOService : nsISupports
out nsIChannel aResult); out nsIChannel aResult);
/** /**
* Equivalent to newChannelFromURI2(aURI, aLoadingNode, ...) * Equivalent to newChannelFromURI(aURI, aLoadingNode, ...)
*/ */
nsIChannel newChannelFromURIWithLoadInfo(in nsIURI aURI, nsIChannel newChannelFromURIWithLoadInfo(in nsIURI aURI,
in nsILoadInfo aLoadInfo); in nsILoadInfo aLoadInfo);
/** /**
* Equivalent to newChannelFromURI2(newURI(...)) * Equivalent to newChannelFromURI(newURI(...))
*/ */
nsIChannel newChannel2(in AUTF8String aSpec, nsIChannel newChannel(in AUTF8String aSpec,
in string aOriginCharset, in string aOriginCharset,
in nsIURI aBaseURI, in nsIURI aBaseURI,
in Node aLoadingNode, in Node aLoadingNode,
in nsIPrincipal aLoadingPrincipal, in nsIPrincipal aLoadingPrincipal,
in nsIPrincipal aTriggeringPrincipal, in nsIPrincipal aTriggeringPrincipal,
in unsigned long aSecurityFlags, in unsigned long aSecurityFlags,
in unsigned long aContentPolicyType); in unsigned long aContentPolicyType);
/** /**
* Returns true if networking is in "offline" mode. When in offline mode, * 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(). * then loadingPrincipal must be equal to loadingNode->NodePrincipal().
* But less error prone is to just supply a loadingNode. * But less error prone is to just supply a loadingNode.
*/ */
nsIChannel newChannelFromURIWithProxyFlags2(in nsIURI aURI, nsIChannel newChannelFromURIWithProxyFlags(in nsIURI aURI,
in nsIURI aProxyURI, in nsIURI aProxyURI,
in unsigned long aProxyFlags, in unsigned long aProxyFlags,
in Node aLoadingNode, in Node aLoadingNode,
in nsIPrincipal aLoadingPrincipal, in nsIPrincipal aLoadingPrincipal,
in nsIPrincipal aTriggeringPrincipal, in nsIPrincipal aTriggeringPrincipal,
in unsigned long aSecurityFlags, in unsigned long aSecurityFlags,
in unsigned long aContentPolicyType); in unsigned long aContentPolicyType);
}; };
%{C++ %{C++

View file

@ -836,18 +836,18 @@ nsIOService::NewFileURI(nsIFile *file, nsIURI **result) {
} }
NS_IMETHODIMP NS_IMETHODIMP
nsIOService::NewChannelFromURI2(nsIURI *aURI, nsINode *aLoadingNode, nsIOService::NewChannelFromURI(nsIURI *aURI, nsINode *aLoadingNode,
nsIPrincipal *aLoadingPrincipal, nsIPrincipal *aLoadingPrincipal,
nsIPrincipal *aTriggeringPrincipal, nsIPrincipal *aTriggeringPrincipal,
uint32_t aSecurityFlags, uint32_t aSecurityFlags,
uint32_t aContentPolicyType, uint32_t aContentPolicyType,
nsIChannel **result) { nsIChannel **result) {
return NewChannelFromURIWithProxyFlags2(aURI, return NewChannelFromURIWithProxyFlags(aURI,
nullptr, // aProxyURI nullptr, // aProxyURI
0, // aProxyFlags 0, // aProxyFlags
aLoadingNode, aLoadingPrincipal, aLoadingNode, aLoadingPrincipal,
aTriggeringPrincipal, aSecurityFlags, aTriggeringPrincipal, aSecurityFlags,
aContentPolicyType, result); aContentPolicyType, result);
} }
nsresult nsIOService::NewChannelFromURIWithClientAndController( nsresult nsIOService::NewChannelFromURIWithClientAndController(
nsIURI *aURI, nsINode *aLoadingNode, nsIPrincipal *aLoadingPrincipal, nsIURI *aURI, nsINode *aLoadingNode, nsIPrincipal *aLoadingPrincipal,
@ -924,8 +924,8 @@ nsresult nsIOService::NewChannelFromURIWithProxyFlagsInternal(
nsCOMPtr<nsIChannel> channel; nsCOMPtr<nsIChannel> channel;
nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler); nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler);
if (pph) { if (pph) {
rv = pph->NewProxiedChannel2(aURI, nullptr, aProxyFlags, aProxyURI, rv = pph->NewProxiedChannel(aURI, nullptr, aProxyFlags, aProxyURI,
aLoadInfo, getter_AddRefs(channel)); aLoadInfo, getter_AddRefs(channel));
} else { } else {
rv = handler->NewChannel(aURI, aLoadInfo, getter_AddRefs(channel)); rv = handler->NewChannel(aURI, aLoadInfo, getter_AddRefs(channel));
} }
@ -976,7 +976,7 @@ nsresult nsIOService::NewChannelFromURIWithProxyFlagsInternal(
} }
NS_IMETHODIMP NS_IMETHODIMP
nsIOService::NewChannelFromURIWithProxyFlags2( nsIOService::NewChannelFromURIWithProxyFlags(
nsIURI *aURI, nsIURI *aProxyURI, uint32_t aProxyFlags, nsIURI *aURI, nsIURI *aProxyURI, uint32_t aProxyFlags,
nsINode *aLoadingNode, nsIPrincipal *aLoadingPrincipal, nsINode *aLoadingNode, nsIPrincipal *aLoadingPrincipal,
nsIPrincipal *aTriggeringPrincipal, uint32_t aSecurityFlags, nsIPrincipal *aTriggeringPrincipal, uint32_t aSecurityFlags,
@ -989,20 +989,20 @@ nsIOService::NewChannelFromURIWithProxyFlags2(
} }
NS_IMETHODIMP NS_IMETHODIMP
nsIOService::NewChannel2(const nsACString &aSpec, const char *aCharset, nsIOService::NewChannel(const nsACString &aSpec, const char *aCharset,
nsIURI *aBaseURI, nsINode *aLoadingNode, nsIURI *aBaseURI, nsINode *aLoadingNode,
nsIPrincipal *aLoadingPrincipal, nsIPrincipal *aLoadingPrincipal,
nsIPrincipal *aTriggeringPrincipal, nsIPrincipal *aTriggeringPrincipal,
uint32_t aSecurityFlags, uint32_t aContentPolicyType, uint32_t aSecurityFlags, uint32_t aContentPolicyType,
nsIChannel **result) { nsIChannel **result) {
nsresult rv; nsresult rv;
nsCOMPtr<nsIURI> uri; nsCOMPtr<nsIURI> uri;
rv = NewURI(aSpec, aCharset, aBaseURI, getter_AddRefs(uri)); rv = NewURI(aSpec, aCharset, aBaseURI, getter_AddRefs(uri));
if (NS_FAILED(rv)) return rv; if (NS_FAILED(rv)) return rv;
return NewChannelFromURI2(uri, aLoadingNode, aLoadingPrincipal, return NewChannelFromURI(uri, aLoadingNode, aLoadingPrincipal,
aTriggeringPrincipal, aSecurityFlags, aTriggeringPrincipal, aSecurityFlags,
aContentPolicyType, result); aContentPolicyType, result);
} }
bool nsIOService::IsLinkUp() { bool nsIOService::IsLinkUp() {
@ -1763,13 +1763,13 @@ nsresult nsIOService::SpeculativeConnectInternal(
// channel we create underneath - hence it's safe to use // channel we create underneath - hence it's safe to use
// the systemPrincipal as the loadingPrincipal for this channel. // the systemPrincipal as the loadingPrincipal for this channel.
nsCOMPtr<nsIChannel> channel; nsCOMPtr<nsIChannel> channel;
rv = NewChannelFromURI2(aURI, rv = NewChannelFromURI(aURI,
nullptr, // aLoadingNode, nullptr, // aLoadingNode,
loadingPrincipal, loadingPrincipal,
nullptr, // aTriggeringPrincipal, nullptr, // aTriggeringPrincipal,
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
nsIContentPolicy::TYPE_SPECULATIVE, nsIContentPolicy::TYPE_SPECULATIVE,
getter_AddRefs(channel)); getter_AddRefs(channel));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
if (aAnonymous) { if (aAnonymous) {

View file

@ -29,27 +29,8 @@ interface nsIProxiedProtocolHandler : nsIProtocolHandler
* a ws:// uri. * a ws:// uri.
* @param aLoadInfo used to evaluate who initated the resource request. * @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, nsIChannel newProxiedChannel(in nsIURI uri, in nsIProxyInfo proxyInfo,
in unsigned long proxyResolveFlags, in unsigned long proxyResolveFlags,
in nsIURI proxyURI); in nsIURI proxyURI,
in nsILoadInfo aLoadInfo);
}; };

View file

@ -1889,7 +1889,7 @@ static nsresult CreateHTTPProxiedChannel(nsIChannel *channel, nsIProxyInfo *pi,
nsCOMPtr<nsILoadInfo> loadInfo; nsCOMPtr<nsILoadInfo> loadInfo;
channel->GetLoadInfo(getter_AddRefs(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 NS_IMETHODIMP

View file

@ -173,15 +173,15 @@ nsFtpProtocolHandler::NewURI(const nsACString &aSpec, const char *aCharset,
NS_IMETHODIMP NS_IMETHODIMP
nsFtpProtocolHandler::NewChannel(nsIURI *url, nsILoadInfo *aLoadInfo, nsFtpProtocolHandler::NewChannel(nsIURI *url, nsILoadInfo *aLoadInfo,
nsIChannel **result) { nsIChannel **result) {
return NewProxiedChannel2(url, nullptr, 0, nullptr, aLoadInfo, result); return NewProxiedChannel(url, nullptr, 0, nullptr, aLoadInfo, result);
} }
NS_IMETHODIMP NS_IMETHODIMP
nsFtpProtocolHandler::NewProxiedChannel2(nsIURI *uri, nsIProxyInfo *proxyInfo, nsFtpProtocolHandler::NewProxiedChannel(nsIURI *uri, nsIProxyInfo *proxyInfo,
uint32_t proxyResolveFlags, uint32_t proxyResolveFlags,
nsIURI *proxyURI, nsIURI *proxyURI,
nsILoadInfo *aLoadInfo, nsILoadInfo *aLoadInfo,
nsIChannel **result) { nsIChannel **result) {
NS_ENSURE_ARG_POINTER(uri); NS_ENSURE_ARG_POINTER(uri);
RefPtr<nsBaseChannel> channel; RefPtr<nsBaseChannel> channel;
if (IsNeckoChild()) if (IsNeckoChild())
@ -204,14 +204,6 @@ nsFtpProtocolHandler::NewProxiedChannel2(nsIURI *uri, nsIProxyInfo *proxyInfo,
return rv; 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 NS_IMETHODIMP
nsFtpProtocolHandler::AllowPort(int32_t port, const char *scheme, nsFtpProtocolHandler::AllowPort(int32_t port, const char *scheme,
bool *_retval) { bool *_retval) {

View file

@ -3119,8 +3119,8 @@ nsresult nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo *pi) {
nsresult rv; nsresult rv;
nsCOMPtr<nsIChannel> newChannel; nsCOMPtr<nsIChannel> newChannel;
rv = gHttpHandler->NewProxiedChannel2(mURI, pi, mProxyResolveFlags, mProxyURI, rv = gHttpHandler->NewProxiedChannel(mURI, pi, mProxyResolveFlags, mProxyURI,
mLoadInfo, getter_AddRefs(newChannel)); mLoadInfo, getter_AddRefs(newChannel));
if (NS_FAILED(rv)) return rv; if (NS_FAILED(rv)) return rv;
uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL; uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;

View file

@ -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 NS_IMETHODIMP
@ -2062,9 +2062,9 @@ nsHttpHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
NS_IMETHODIMP NS_IMETHODIMP
nsHttpHandler::NewProxiedChannel2(nsIURI *uri, nsIProxyInfo *givenProxyInfo, nsHttpHandler::NewProxiedChannel(nsIURI *uri, nsIProxyInfo *givenProxyInfo,
uint32_t proxyResolveFlags, nsIURI *proxyURI, uint32_t proxyResolveFlags, nsIURI *proxyURI,
nsILoadInfo *aLoadInfo, nsIChannel **result) { nsILoadInfo *aLoadInfo, nsIChannel **result) {
RefPtr<HttpBaseChannel> httpChannel; RefPtr<HttpBaseChannel> httpChannel;
LOG(("nsHttpHandler::NewProxiedChannel [proxyInfo=%p]\n", givenProxyInfo)); LOG(("nsHttpHandler::NewProxiedChannel [proxyInfo=%p]\n", givenProxyInfo));
@ -2127,14 +2127,6 @@ nsHttpHandler::NewProxiedChannel2(nsIURI *uri, nsIProxyInfo *givenProxyInfo,
return NS_OK; 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 // nsHttpHandler::nsIHttpProtocolHandler
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

View file

@ -60,7 +60,7 @@ nsresult nsViewSourceChannel::Init(nsIURI *uri) {
return NS_ERROR_INVALID_ARG; 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. // and sets the right loadInfo right after returning from this function.
// Until then we follow the principal of least privilege and use // Until then we follow the principal of least privilege and use
// nullPrincipal as the loadingPrincipal and the least permissive // nullPrincipal as the loadingPrincipal and the least permissive
@ -68,7 +68,7 @@ nsresult nsViewSourceChannel::Init(nsIURI *uri) {
nsCOMPtr<nsIPrincipal> nullPrincipal = nsCOMPtr<nsIPrincipal> nullPrincipal =
mozilla::NullPrincipal::CreateWithoutOriginAttributes(); mozilla::NullPrincipal::CreateWithoutOriginAttributes();
rv = pService->NewChannel2( rv = pService->NewChannel(
path, path,
nullptr, // aOriginCharset nullptr, // aOriginCharset
nullptr, // aCharSet nullptr, // aCharSet

View file

@ -3387,7 +3387,7 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI, const nsACString &aOrigin,
// Ideally we'd call newChannelFromURIWithLoadInfo here, but that doesn't // Ideally we'd call newChannelFromURIWithLoadInfo here, but that doesn't
// allow setting proxy uri/flags // allow setting proxy uri/flags
rv = ioService->NewChannelFromURIWithProxyFlags2( rv = ioService->NewChannelFromURIWithProxyFlags(
localURI, mURI, localURI, mURI,
nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY | nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL, nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,

View file

@ -513,14 +513,14 @@ function test_newChannel_with_string()
// Check that we get the same URI back from channel the IO service creates and // Check that we get the same URI back from channel the IO service creates and
// the channel the utility method creates. // the channel the utility method creates.
let ios = Services.io let ios = Services.io
let iosChannel = ios.newChannel2(TEST_SPEC, let iosChannel = ios.newChannel(TEST_SPEC,
null, null,
null, null,
null, // aLoadingNode null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(), Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER); Ci.nsIContentPolicy.TYPE_OTHER);
let NetUtilChannel = NetUtil.newChannel({ let NetUtilChannel = NetUtil.newChannel({
uri: TEST_SPEC, uri: TEST_SPEC,
loadUsingSystemPrincipal: true 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 // Check that we get the same URI back from channel the IO service creates and
// the channel the utility method creates. // the channel the utility method creates.
let uri = NetUtil.newURI(TEST_SPEC); let uri = NetUtil.newURI(TEST_SPEC);
let iosChannel = Services.io.newChannelFromURI2(uri, let iosChannel = Services.io.newChannelFromURI(uri,
null, // aLoadingNode null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(), Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER); Ci.nsIContentPolicy.TYPE_OTHER);
let NetUtilChannel = NetUtil.newChannel({ let NetUtilChannel = NetUtil.newChannel({
uri: uri, uri: uri,
loadUsingSystemPrincipal: true loadUsingSystemPrincipal: true
@ -556,12 +556,12 @@ function test_newChannel_with_options()
{ {
let uri = "data:text/plain,"; let uri = "data:text/plain,";
let iosChannel = Services.io.newChannelFromURI2(NetUtil.newURI(uri), let iosChannel = Services.io.newChannelFromURI(NetUtil.newURI(uri),
null, // aLoadingNode null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(), Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER); Ci.nsIContentPolicy.TYPE_OTHER);
function checkEqualToIOSChannel(channel) { function checkEqualToIOSChannel(channel) {
Assert.ok(iosChannel.URI.equals(channel.URI)); Assert.ok(iosChannel.URI.equals(channel.URI));

View file

@ -108,14 +108,14 @@ add_task(async function testDirectProxy() {
let ioService = Cc["@mozilla.org/network/io-service;1"]. let ioService = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService); getService(Ci.nsIIOService);
let chan = ioService. let chan = ioService.
newChannelFromURIWithProxyFlags2(uri, newChannelFromURIWithProxyFlags(uri,
proxyURI, proxyURI,
0, 0,
null, null,
Services.scriptSecurityManager.getSystemPrincipal(), Services.scriptSecurityManager.getSystemPrincipal(),
null, null,
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER); Ci.nsIContentPolicy.TYPE_OTHER);
let pi = await TestProxyType(chan, 0); let pi = await TestProxyType(chan, 0);
equal(pi, null, "Expected proxy host to be null"); 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"]. let ioService = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService); getService(Ci.nsIIOService);
let chan = ioService. let chan = ioService.
newChannelFromURIWithProxyFlags2(uri, newChannelFromURIWithProxyFlags(uri,
proxyURI, proxyURI,
proxyFlags, proxyFlags,
null, null,
Services.scriptSecurityManager.getSystemPrincipal(), Services.scriptSecurityManager.getSystemPrincipal(),
null, null,
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER); Ci.nsIContentPolicy.TYPE_OTHER);
let pi = await TestProxyType(chan, proxyFlags); let pi = await TestProxyType(chan, proxyFlags);
equal(pi.host, "localhost", "Expected proxy host to be localhost"); equal(pi.host, "localhost", "Expected proxy host to be localhost");

View file

@ -239,12 +239,12 @@ OCSPRequest::Run() {
} }
nsCOMPtr<nsIChannel> channel; nsCOMPtr<nsIChannel> channel;
rv = ios->NewChannel2(mAIALocation, nullptr, nullptr, rv = ios->NewChannel(mAIALocation, nullptr, nullptr,
nullptr, // aLoadingNode nullptr, // aLoadingNode
nsContentUtils::GetSystemPrincipal(), nsContentUtils::GetSystemPrincipal(),
nullptr, // aTriggeringPrincipal nullptr, // aTriggeringPrincipal
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
nsIContentPolicy::TYPE_OTHER, getter_AddRefs(channel)); nsIContentPolicy::TYPE_OTHER, getter_AddRefs(channel));
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return NotifyDone(rv, lock); return NotifyDone(rv, lock);
} }

View file

@ -1 +1 @@
b7713856ebf2 1f04eea8834a

View file

@ -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

View file

@ -56,6 +56,7 @@ NSS_SRCDIRS = \
p7sign \ p7sign \
p7verify \ p7verify \
pk12util \ pk12util \
pk11importtest \
pk11ectest \ pk11ectest \
pk11gcmtest \ pk11gcmtest \
pk11mode \ pk11mode \

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -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'
}
}

View file

@ -10,4 +10,3 @@
*/ */
#error "Do not include this header file." #error "Do not include this header file."

View file

@ -11,6 +11,7 @@
#include "cert.h" #include "cert.h"
#include "keyhi.h" #include "keyhi.h"
#include "p12.h" #include "p12.h"
#include "pk11pqg.h"
#include "pk11pub.h" #include "pk11pub.h"
#include "pkcs11uri.h" #include "pkcs11uri.h"
@ -41,6 +42,7 @@ struct ScopedDelete {
void operator()(PLArenaPool* arena) { PORT_FreeArena(arena, PR_FALSE); } void operator()(PLArenaPool* arena) { PORT_FreeArena(arena, PR_FALSE); }
void operator()(PK11Context* context) { PK11_DestroyContext(context, true); } void operator()(PK11Context* context) { PK11_DestroyContext(context, true); }
void operator()(PK11GenericObject* obj) { PK11_DestroyGenericObject(obj); } void operator()(PK11GenericObject* obj) { PK11_DestroyGenericObject(obj); }
void operator()(PQGParams* pqg) { PK11_PQG_DestroyParams(pqg); }
void operator()(SEC_PKCS12DecoderContext* dcx) { void operator()(SEC_PKCS12DecoderContext* dcx) {
SEC_PKCS12DecoderFinish(dcx); SEC_PKCS12DecoderFinish(dcx);
} }
@ -66,6 +68,7 @@ SCOPED(CERTName);
SCOPED(CERTSubjectPublicKeyInfo); SCOPED(CERTSubjectPublicKeyInfo);
SCOPED(PK11SlotInfo); SCOPED(PK11SlotInfo);
SCOPED(PK11SymKey); SCOPED(PK11SymKey);
SCOPED(PQGParams);
SCOPED(PRFileDesc); SCOPED(PRFileDesc);
SCOPED(SECAlgorithmID); SCOPED(SECAlgorithmID);
SCOPED(SECKEYEncryptedPrivateKeyInfo); SCOPED(SECKEYEncryptedPrivateKeyInfo);
@ -82,4 +85,9 @@ SCOPED(CERTDistNames);
#undef SCOPED #undef SCOPED
struct StackSECItem : public SECItem {
StackSECItem() : SECItem({siBuffer, nullptr, 0}) {}
~StackSECItem() { SECITEM_FreeItem(this, PR_FALSE); }
};
#endif // nss_scoped_ptrs_h__ #endif // nss_scoped_ptrs_h__

View file

@ -33,6 +33,7 @@ struct ScopedMaybeDelete {
SCOPED(SECAlgorithmID); SCOPED(SECAlgorithmID);
SCOPED(SECItem); SCOPED(SECItem);
SCOPED(PK11URI); SCOPED(PK11URI);
SCOPED(PLArenaPool);
#undef SCOPED #undef SCOPED

View file

@ -80,6 +80,32 @@ inline std::ostream& operator<<(std::ostream& os, SSLProtocolVariant v) {
return os << ((v == ssl_variant_stream) ? "TLS" : "DTLS"); 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<int>(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<int>(v);
}
inline bool IsDtls(uint16_t version) { return (version & 0x8000) == 0x8000; } inline bool IsDtls(uint16_t version) { return (version & 0x8000) == 0x8000; }
inline uint16_t NormalizeTlsVersion(uint16_t version) { inline uint16_t NormalizeTlsVersion(uint16_t version) {

View file

@ -10,7 +10,23 @@
int main(int argc, char **argv) { int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, 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 <dir> [-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; return 1;
} }
if (NSS_SetDomesticPolicy() != SECSuccess) { if (NSS_SetDomesticPolicy() != SECSuccess) {

View file

@ -13,6 +13,7 @@ CPPSRCS = \
pk11_ecdsa_unittest.cc \ pk11_ecdsa_unittest.cc \
pk11_encrypt_derive_unittest.cc \ pk11_encrypt_derive_unittest.cc \
pk11_export_unittest.cc \ pk11_export_unittest.cc \
pk11_import_unittest.cc \
pk11_pbkdf2_unittest.cc \ pk11_pbkdf2_unittest.cc \
pk11_prf_unittest.cc \ pk11_prf_unittest.cc \
pk11_prng_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)cpputil.$(LIB_SUFFIX) \
$(DIST)/lib/$(LIB_PREFIX)gtestutil.$(LIB_SUFFIX) \ $(DIST)/lib/$(LIB_PREFIX)gtestutil.$(LIB_SUFFIX) \
$(NULL) $(NULL)

View file

@ -18,6 +18,7 @@
'pk11_curve25519_unittest.cc', 'pk11_curve25519_unittest.cc',
'pk11_ecdsa_unittest.cc', 'pk11_ecdsa_unittest.cc',
'pk11_encrypt_derive_unittest.cc', 'pk11_encrypt_derive_unittest.cc',
'pk11_import_unittest.cc',
'pk11_pbkdf2_unittest.cc', 'pk11_pbkdf2_unittest.cc',
'pk11_prf_unittest.cc', 'pk11_prf_unittest.cc',
'pk11_prng_unittest.cc', 'pk11_prng_unittest.cc',

View file

@ -297,38 +297,6 @@ SSLKEAType SSLInt_GetKEAType(SSLNamedGroup group) {
return groupDef->keaType; 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) { void SSLInt_SetTicketLifetime(uint32_t lifetime) {
ssl_ticket_lifetime = lifetime; ssl_ticket_lifetime = lifetime;
} }
@ -360,16 +328,14 @@ void SSLInt_RolloverAntiReplay(void) {
tls13_AntiReplayRollover(ssl_TimeUsec()); tls13_AntiReplayRollover(ssl_TimeUsec());
} }
SECStatus SSLInt_GetEpochs(PRFileDesc *fd, PRUint16 *readEpoch, SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending) {
PRUint16 *writeEpoch) {
sslSocket *ss = ssl_FindSocket(fd); sslSocket *ss = ssl_FindSocket(fd);
if (!ss || !readEpoch || !writeEpoch) { if (!ss) {
return SECFailure; return SECFailure;
} }
ssl_GetSpecReadLock(ss); ssl_GetSSL3HandshakeLock(ss);
*readEpoch = ss->ssl3.crSpec->epoch; *pending = ss->ssl3.hs.msg_body.len > 0;
*writeEpoch = ss->ssl3.cwSpec->epoch; ssl_ReleaseSSL3HandshakeLock(ss);
ssl_ReleaseSpecReadLock(ss);
return SECSuccess; return SECSuccess;
} }

View file

@ -39,16 +39,7 @@ SECStatus SSLInt_AdvanceWriteSeqNum(PRFileDesc *fd, PRUint64 to);
SECStatus SSLInt_AdvanceReadSeqNum(PRFileDesc *fd, PRUint64 to); SECStatus SSLInt_AdvanceReadSeqNum(PRFileDesc *fd, PRUint64 to);
SECStatus SSLInt_AdvanceWriteSeqByAWindow(PRFileDesc *fd, PRInt32 extra); SECStatus SSLInt_AdvanceWriteSeqByAWindow(PRFileDesc *fd, PRInt32 extra);
SSLKEAType SSLInt_GetKEAType(SSLNamedGroup group); SSLKEAType SSLInt_GetKEAType(SSLNamedGroup group);
SECStatus SSLInt_GetEpochs(PRFileDesc *fd, PRUint16 *readEpoch, SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending);
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);
void SSLInt_SetTicketLifetime(uint32_t lifetime); void SSLInt_SetTicketLifetime(uint32_t lifetime);
SECStatus SSLInt_SetSocketMaxEarlyDataSize(PRFileDesc *fd, uint32_t size); SECStatus SSLInt_SetSocketMaxEarlyDataSize(PRFileDesc *fd, uint32_t size);
void SSLInt_RolloverAntiReplay(void); void SSLInt_RolloverAntiReplay(void);

View file

@ -36,6 +36,7 @@ CPPSRCS = \
ssl_loopback_unittest.cc \ ssl_loopback_unittest.cc \
ssl_misc_unittest.cc \ ssl_misc_unittest.cc \
ssl_record_unittest.cc \ ssl_record_unittest.cc \
ssl_recordsep_unittest.cc \
ssl_recordsize_unittest.cc \ ssl_recordsize_unittest.cc \
ssl_resumption_unittest.cc \ ssl_resumption_unittest.cc \
ssl_renegotiation_unittest.cc \ ssl_renegotiation_unittest.cc \

View file

@ -176,6 +176,321 @@ TEST_P(TlsConnectGeneric, ClientAuth) {
CheckKeys(); CheckKeys();
} }
class TlsCertificateRequestContextRecorder : public TlsHandshakeFilter {
public:
TlsCertificateRequestContextRecorder(const std::shared_ptr<TlsAgent>& 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<TlsCertificateRequestContextRecorder>(
server_, kTlsHandshakeCertificateRequest);
auto capture_certificate =
MakeTlsFilter<TlsCertificateRequestContextRecorder>(
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<TlsAgent>& 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<TlsDamageCertificateRequestContextFilter>(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<TlsAgent>& 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<TlsDamageSignatureFilter>(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<TlsCertificateRequestContextRecorder>(
server_, kTlsHandshakeCertificateRequest);
auto capture_certificate =
MakeTlsFilter<TlsCertificateRequestContextRecorder>(
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 // In TLS 1.3, the client sends its cert rejection on the
// second flight, and since it has already received the // second flight, and since it has already received the
// server's Finished, it transitions to complete and // server's Finished, it transitions to complete and
@ -273,9 +588,7 @@ class TlsReplaceSignatureSchemeFilter : public TlsHandshakeFilter {
TlsReplaceSignatureSchemeFilter(const std::shared_ptr<TlsAgent>& a, TlsReplaceSignatureSchemeFilter(const std::shared_ptr<TlsAgent>& a,
SSLSignatureScheme scheme) SSLSignatureScheme scheme)
: TlsHandshakeFilter(a, {kTlsHandshakeCertificateVerify}), : TlsHandshakeFilter(a, {kTlsHandshakeCertificateVerify}),
scheme_(scheme) { scheme_(scheme) {}
EnableDecryption();
}
protected: protected:
virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header, virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header,
@ -552,7 +865,9 @@ TEST_P(TlsConnectTls12, SignatureAlgorithmDrop) {
TEST_P(TlsConnectTls13, UnsupportedSignatureSchemeAlert) { TEST_P(TlsConnectTls13, UnsupportedSignatureSchemeAlert) {
EnsureTlsSetup(); EnsureTlsSetup();
MakeTlsFilter<TlsReplaceSignatureSchemeFilter>(server_, ssl_sig_none); auto filter =
MakeTlsFilter<TlsReplaceSignatureSchemeFilter>(server_, ssl_sig_none);
filter->EnableDecryption();
ConnectExpectAlert(client_, kTlsAlertIllegalParameter); ConnectExpectAlert(client_, kTlsAlertIllegalParameter);
server_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); server_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT);
@ -563,8 +878,9 @@ TEST_P(TlsConnectTls13, InconsistentSignatureSchemeAlert) {
EnsureTlsSetup(); EnsureTlsSetup();
// This won't work because we use an RSA cert by default. // This won't work because we use an RSA cert by default.
MakeTlsFilter<TlsReplaceSignatureSchemeFilter>( auto filter = MakeTlsFilter<TlsReplaceSignatureSchemeFilter>(
server_, ssl_sig_ecdsa_secp256r1_sha256); server_, ssl_sig_ecdsa_secp256r1_sha256);
filter->EnableDecryption();
ConnectExpectAlert(client_, kTlsAlertIllegalParameter); ConnectExpectAlert(client_, kTlsAlertIllegalParameter);
server_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); server_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT);

View file

@ -62,7 +62,6 @@ TEST_P(TlsConnectGenericPre13, DamageServerSignature) {
EnsureTlsSetup(); EnsureTlsSetup();
auto filter = MakeTlsFilter<TlsLastByteDamager>( auto filter = MakeTlsFilter<TlsLastByteDamager>(
server_, kTlsHandshakeServerKeyExchange); server_, kTlsHandshakeServerKeyExchange);
filter->EnableDecryption();
ExpectAlert(client_, kTlsAlertDecryptError); ExpectAlert(client_, kTlsAlertDecryptError);
ConnectExpectFail(); ConnectExpectFail();
client_->CheckErrorCode(SEC_ERROR_BAD_SIGNATURE); client_->CheckErrorCode(SEC_ERROR_BAD_SIGNATURE);
@ -84,7 +83,9 @@ TEST_P(TlsConnectGeneric, DamageClientSignature) {
server_->RequestClientAuth(true); server_->RequestClientAuth(true);
auto filter = MakeTlsFilter<TlsLastByteDamager>( auto filter = MakeTlsFilter<TlsLastByteDamager>(
client_, kTlsHandshakeCertificateVerify); client_, kTlsHandshakeCertificateVerify);
filter->EnableDecryption(); if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
filter->EnableDecryption();
}
server_->ExpectSendAlert(kTlsAlertDecryptError); server_->ExpectSendAlert(kTlsAlertDecryptError);
// Do these handshakes by hand to avoid race condition on // Do these handshakes by hand to avoid race condition on
// the client processing the server's alert. // the client processing the server's alert.

View file

@ -66,6 +66,38 @@ TEST_P(TlsConnectDatagramPre13, DropServerSecondFlightThrice) {
Connect(); Connect();
} }
static void CheckAcks(const std::shared_ptr<TlsRecordRecorder>& acks,
size_t index, std::vector<uint64_t> 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<size_t>(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, class TlsDropDatagram13 : public TlsConnectDatagram13,
public ::testing::WithParamInterface<bool> { public ::testing::WithParamInterface<bool> {
public: public:
@ -139,37 +171,6 @@ class TlsDropDatagram13 : public TlsConnectDatagram13,
std::shared_ptr<PacketFilter> chain_; std::shared_ptr<PacketFilter> chain_;
}; };
void CheckAcks(const DropAckChain& chain, size_t index,
std::vector<uint64_t> 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<size_t>(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() { void CheckedHandshakeSendReceive() {
Handshake(); Handshake();
CheckPostHandshake(); CheckPostHandshake();
@ -199,7 +200,7 @@ TEST_P(TlsDropDatagram13, DropClientFirstFlightOnce) {
client_->Handshake(); client_->Handshake();
server_->Handshake(); server_->Handshake();
CheckedHandshakeSendReceive(); CheckedHandshakeSendReceive();
CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL});
} }
TEST_P(TlsDropDatagram13, DropServerFirstFlightOnce) { TEST_P(TlsDropDatagram13, DropServerFirstFlightOnce) {
@ -210,7 +211,7 @@ TEST_P(TlsDropDatagram13, DropServerFirstFlightOnce) {
server_->Handshake(); server_->Handshake();
server_filters_.drop_->Disable(); server_filters_.drop_->Disable();
CheckedHandshakeSendReceive(); CheckedHandshakeSendReceive();
CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL});
} }
// Dropping the server's first record also does not produce // Dropping the server's first record also does not produce
@ -223,7 +224,7 @@ TEST_P(TlsDropDatagram13, DropServerFirstRecordOnce) {
server_->Handshake(); server_->Handshake();
Handshake(); Handshake();
CheckedHandshakeSendReceive(); CheckedHandshakeSendReceive();
CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL});
} }
// Dropping the second packet of the server's flight should // Dropping the second packet of the server's flight should
@ -236,8 +237,8 @@ TEST_P(TlsDropDatagram13, DropServerSecondRecordOnce) {
HandshakeAndAck(client_); HandshakeAndAck(client_);
expected_client_acks_ = 1; expected_client_acks_ = 1;
CheckedHandshakeSendReceive(); CheckedHandshakeSendReceive();
CheckAcks(client_filters_, 0, {0}); // ServerHello CheckAcks(client_filters_.ack_, 0, {0}); // ServerHello
CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL});
} }
// Drop the server ACK and verify that the client retransmits // 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()); EXPECT_EQ(PR_WOULD_BLOCK_ERROR, PORT_GetError());
CheckPostHandshake(); CheckPostHandshake();
// There should be two copies of the finished ACK // There should be two copies of the finished ACK
CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL});
CheckAcks(server_filters_, 1, {0x0002000000000000ULL}); CheckAcks(server_filters_.ack_, 1, {0x0002000000000000ULL});
} }
// Drop the client certificate verify. // Drop the client certificate verify.
@ -281,10 +282,10 @@ TEST_P(TlsDropDatagram13, DropClientCertVerify) {
expected_server_acks_ = 2; expected_server_acks_ = 2;
CheckedHandshakeSendReceive(); CheckedHandshakeSendReceive();
// Ack of the Cert. // Ack of the Cert.
CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL});
// Ack of the whole client handshake. // Ack of the whole client handshake.
CheckAcks( CheckAcks(
server_filters_, 1, server_filters_.ack_, 1,
{0x0002000000000000ULL, // CH (we drop everything after this on client) {0x0002000000000000ULL, // CH (we drop everything after this on client)
0x0002000000000003ULL, // CT (2) 0x0002000000000003ULL, // CT (2)
0x0002000000000004ULL}); // FIN (2) 0x0002000000000004ULL}); // FIN (2)
@ -310,11 +311,11 @@ TEST_P(TlsDropDatagram13, DropFirstHalfOfServerCertificate) {
// as the previous CT1). // as the previous CT1).
EXPECT_EQ(ct1_size, server_filters_.record(0).buffer.len()); EXPECT_EQ(ct1_size, server_filters_.record(0).buffer.len());
CheckedHandshakeSendReceive(); CheckedHandshakeSendReceive();
CheckAcks(client_filters_, 0, CheckAcks(client_filters_.ack_, 0,
{0, // SH {0, // SH
0x0002000000000000ULL, // EE 0x0002000000000000ULL, // EE
0x0002000000000002ULL}); // CT2 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. // 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 // Check that the first record is CT1
EXPECT_EQ(ct1_size, server_filters_.record(0).buffer.len()); EXPECT_EQ(ct1_size, server_filters_.record(0).buffer.len());
CheckedHandshakeSendReceive(); CheckedHandshakeSendReceive();
CheckAcks(client_filters_, 0, CheckAcks(client_filters_.ack_, 0,
{ {
0, // SH 0, // SH
0x0002000000000000ULL, // EE 0x0002000000000000ULL, // EE
0x0002000000000001ULL, // CT1 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 // 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 0, // SH
0x0002000000000000ULL // EE 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). // And from the second attempt for the half was kept (we delayed this ACK).
client_acks.push_back(0x0002000000000000ULL + second_flight_count + client_acks.push_back(0x0002000000000000ULL + second_flight_count +
~dropped_half % 2); ~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. // And the third attempt where the first and last thirds got through.
client_acks.push_back(0x0002000000000000ULL + second_flight_count + client_acks.push_back(0x0002000000000000ULL + second_flight_count +
third_flight_count - 1); third_flight_count - 1);
client_acks.push_back(0x0002000000000000ULL + second_flight_count + client_acks.push_back(0x0002000000000000ULL + second_flight_count +
third_flight_count + 1); third_flight_count + 1);
CheckAcks(client_filters_, 2, client_acks); CheckAcks(client_filters_.ack_, 2, client_acks);
CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL});
} }
private: private:
@ -548,7 +549,7 @@ TEST_P(TlsDropDatagram13, NoDropsDuringZeroRtt) {
CheckConnected(); CheckConnected();
SendReceive(); SendReceive();
EXPECT_EQ(0U, client_filters_.ack_->count()); EXPECT_EQ(0U, client_filters_.ack_->count());
CheckAcks(server_filters_, 0, CheckAcks(server_filters_.ack_, 0,
{0x0001000000000001ULL, // EOED {0x0001000000000001ULL, // EOED
0x0002000000000000ULL}); // Finished 0x0002000000000000ULL}); // Finished
} }
@ -567,8 +568,8 @@ TEST_P(TlsDropDatagram13, DropEEDuringZeroRtt) {
ExpectEarlyDataAccepted(true); ExpectEarlyDataAccepted(true);
CheckConnected(); CheckConnected();
SendReceive(); SendReceive();
CheckAcks(client_filters_, 0, {0}); CheckAcks(client_filters_.ack_, 0, {0});
CheckAcks(server_filters_, 0, CheckAcks(server_filters_.ack_, 0,
{0x0001000000000002ULL, // EOED {0x0001000000000002ULL, // EOED
0x0002000000000000ULL}); // Finished 0x0002000000000000ULL}); // Finished
} }
@ -608,22 +609,22 @@ TEST_P(TlsDropDatagram13, ReorderServerEE) {
expected_client_acks_ = 1; expected_client_acks_ = 1;
HandshakeAndAck(client_); HandshakeAndAck(client_);
CheckedHandshakeSendReceive(); CheckedHandshakeSendReceive();
CheckAcks(client_filters_, 0, CheckAcks(client_filters_.ack_, 0,
{ {
0, // SH 0, // SH
0x0002000000000000, // EE 0x0002000000000000, // EE
}); });
CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL});
} }
// The client sends an out of order non-handshake message // The client sends an out of order non-handshake message
// but with the handshake key. // but with the handshake key.
class TlsSendCipherSpecCapturer { class TlsSendCipherSpecCapturer {
public: public:
TlsSendCipherSpecCapturer(std::shared_ptr<TlsAgent>& agent) TlsSendCipherSpecCapturer(const std::shared_ptr<TlsAgent>& agent)
: send_cipher_specs_() { : agent_(agent), send_cipher_specs_() {
SSLInt_SetCipherSpecChangeFunc(agent->ssl_fd(), CipherSpecChanged, EXPECT_EQ(SECSuccess,
(void*)this); SSL_SecretCallback(agent_->ssl_fd(), SecretCallback, this));
} }
std::shared_ptr<TlsCipherSpec> spec(size_t i) { std::shared_ptr<TlsCipherSpec> spec(size_t i) {
@ -634,28 +635,42 @@ class TlsSendCipherSpecCapturer {
} }
private: private:
static void CipherSpecChanged(void* arg, PRBool sending, static void SecretCallback(PRFileDesc* fd, PRUint16 epoch,
ssl3CipherSpec* newSpec) { SSLSecretDirection dir, PK11SymKey* secret,
if (!sending) { void* arg) {
auto self = static_cast<TlsSendCipherSpecCapturer*>(arg);
std::cerr << self->agent_->role_str() << ": capture " << dir
<< " secret for epoch " << epoch << std::endl;
if (dir == ssl_secret_read) {
return; return;
} }
auto self = static_cast<TlsSendCipherSpecCapturer*>(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<TlsCipherSpec>(); SSLCipherSuiteInfo cipherinfo;
bool ret = spec->Init(SSLInt_CipherSpecToEpoch(newSpec), EXPECT_EQ(SECSuccess,
SSLInt_CipherSpecToAlgorithm(newSpec), SSL_GetCipherSuiteInfo(preinfo.cipherSuite, &cipherinfo,
SSLInt_CipherSpecToKey(newSpec), sizeof(cipherinfo)));
SSLInt_CipherSpecToIv(newSpec)); EXPECT_EQ(sizeof(cipherinfo), cipherinfo.length);
EXPECT_EQ(true, ret);
auto spec = std::make_shared<TlsCipherSpec>(true, epoch);
EXPECT_TRUE(spec->SetKeys(&cipherinfo, secret));
self->send_cipher_specs_.push_back(spec); self->send_cipher_specs_.push_back(spec);
} }
std::shared_ptr<TlsAgent> agent_;
std::vector<std::shared_ptr<TlsCipherSpec>> send_cipher_specs_; std::vector<std::shared_ptr<TlsCipherSpec>> send_cipher_specs_;
}; };
TEST_P(TlsDropDatagram13, SendOutOfOrderAppWithHandshakeKey) { TEST_F(TlsConnectDatagram13, SendOutOfOrderAppWithHandshakeKey) {
StartConnect(); StartConnect();
// Capturing secrets means that we can't use decrypting filters on the client.
TlsSendCipherSpecCapturer capturer(client_); TlsSendCipherSpecCapturer capturer(client_);
client_->Handshake(); client_->Handshake();
server_->Handshake(); server_->Handshake();
@ -680,9 +695,12 @@ TEST_P(TlsDropDatagram13, SendOutOfOrderAppWithHandshakeKey) {
EXPECT_EQ(SSL_ERROR_RX_UNKNOWN_RECORD_TYPE, PORT_GetError()); EXPECT_EQ(SSL_ERROR_RX_UNKNOWN_RECORD_TYPE, PORT_GetError());
} }
TEST_P(TlsDropDatagram13, SendOutOfOrderHsNonsenseWithHandshakeKey) { TEST_F(TlsConnectDatagram13, SendOutOfOrderHsNonsenseWithHandshakeKey) {
StartConnect(); StartConnect();
TlsSendCipherSpecCapturer capturer(client_); TlsSendCipherSpecCapturer capturer(client_);
auto acks = MakeTlsFilter<TlsRecordRecorder>(server_, ssl_ct_ack);
acks->EnableDecryption();
client_->Handshake(); client_->Handshake();
server_->Handshake(); server_->Handshake();
client_->Handshake(); client_->Handshake();
@ -699,10 +717,10 @@ TEST_P(TlsDropDatagram13, SendOutOfOrderHsNonsenseWithHandshakeKey) {
ssl_ct_handshake, ssl_ct_handshake,
DataBuffer(buf, sizeof(buf)))); DataBuffer(buf, sizeof(buf))));
server_->Handshake(); server_->Handshake();
EXPECT_EQ(2UL, server_filters_.ack_->count()); EXPECT_EQ(2UL, acks->count());
// The server acknowledges client Finished twice. // The server acknowledges client Finished twice.
CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); CheckAcks(acks, 0, {0x0002000000000000ULL});
CheckAcks(server_filters_, 1, {0x0002000000000000ULL}); CheckAcks(acks, 1, {0x0002000000000000ULL});
} }
// Shrink the MTU down so that certs get split and then swap the first and // Shrink the MTU down so that certs get split and then swap the first and
@ -726,7 +744,7 @@ TEST_P(TlsReorderDatagram13, ReorderServerCertificate) {
ShiftDtlsTimers(); ShiftDtlsTimers();
CheckedHandshakeSendReceive(); CheckedHandshakeSendReceive();
EXPECT_EQ(2UL, server_filters_.records_->count()); // ACK + Data EXPECT_EQ(2UL, server_filters_.records_->count()); // ACK + Data
CheckAcks(server_filters_, 0, {0x0002000000000000ULL}); CheckAcks(server_filters_.ack_, 0, {0x0002000000000000ULL});
} }
TEST_P(TlsReorderDatagram13, DataAfterEOEDDuringZeroRtt) { TEST_P(TlsReorderDatagram13, DataAfterEOEDDuringZeroRtt) {
@ -761,7 +779,8 @@ TEST_P(TlsReorderDatagram13, DataAfterEOEDDuringZeroRtt) {
CheckConnected(); CheckConnected();
EXPECT_EQ(0U, client_filters_.ack_->count()); EXPECT_EQ(0U, client_filters_.ack_->count());
// Acknowledgements for EOED and Finished. // Acknowledgements for EOED and Finished.
CheckAcks(server_filters_, 0, {0x0001000000000002ULL, 0x0002000000000000ULL}); CheckAcks(server_filters_.ack_, 0,
{0x0001000000000002ULL, 0x0002000000000000ULL});
uint8_t buf[8]; uint8_t buf[8];
rv = PR_Read(server_->ssl_fd(), buf, sizeof(buf)); rv = PR_Read(server_->ssl_fd(), buf, sizeof(buf));
EXPECT_EQ(-1, rv); EXPECT_EQ(-1, rv);
@ -800,7 +819,8 @@ TEST_P(TlsReorderDatagram13, DataAfterFinDuringZeroRtt) {
CheckConnected(); CheckConnected();
EXPECT_EQ(0U, client_filters_.ack_->count()); EXPECT_EQ(0U, client_filters_.ack_->count());
// Acknowledgements for EOED and Finished. // Acknowledgements for EOED and Finished.
CheckAcks(server_filters_, 0, {0x0001000000000002ULL, 0x0002000000000000ULL}); CheckAcks(server_filters_.ack_, 0,
{0x0001000000000002ULL, 0x0002000000000000ULL});
uint8_t buf[8]; uint8_t buf[8];
rv = PR_Read(server_->ssl_fd(), buf, sizeof(buf)); rv = PR_Read(server_->ssl_fd(), buf, sizeof(buf));
EXPECT_EQ(-1, rv); EXPECT_EQ(-1, rv);

View file

@ -37,6 +37,7 @@
'ssl_loopback_unittest.cc', 'ssl_loopback_unittest.cc',
'ssl_misc_unittest.cc', 'ssl_misc_unittest.cc',
'ssl_record_unittest.cc', 'ssl_record_unittest.cc',
'ssl_recordsep_unittest.cc',
'ssl_recordsize_unittest.cc', 'ssl_recordsize_unittest.cc',
'ssl_resumption_unittest.cc', 'ssl_resumption_unittest.cc',
'ssl_renegotiation_unittest.cc', 'ssl_renegotiation_unittest.cc',

View file

@ -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 <queue>
#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<TlsAgent>& 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<HandshakeSecretTracker*>(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<TlsAgent> 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<TlsAgent>& 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<KeyUpdateTracker*>(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<TlsAgent> 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<TlsAgent>& 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<PRFilePrivate*>(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<TlsAgent>& 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<TlsAgent>& 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<TlsAgent>& peer,
TlsAgent::State expected_state) {
ForwardAll(peer);
peer->Handshake();
EXPECT_EQ(expected_state, peer->state());
}
void ForwardPartial(std::shared_ptr<TlsAgent>& 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<TlsAgent>& 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<unsigned int>(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<StagedRecords*>(arg);
stage->records_.push_back(StagedRecord(stage->agent_->role_str(), epoch,
content_type, data,
static_cast<size_t>(len)));
return SECSuccess;
}
std::shared_ptr<TlsAgent>& agent_;
std::deque<StagedRecord> records_;
};
// Attempting to feed application data in before the handshake is complete
// should be caught.
static void RefuseApplicationData(std::shared_ptr<TlsAgent>& 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<unsigned int>(sizeof(d))));
EXPECT_EQ(SEC_ERROR_INVALID_ARGS, PORT_GetError());
}
static void SendForwardReceive(std::shared_ptr<TlsAgent>& sender,
StagedRecords& sender_stage,
std::shared_ptr<TlsAgent>& 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

View file

@ -123,9 +123,11 @@ TEST_P(TlsConnectGeneric, RecordSizeMaximum) {
EnsureTlsSetup(); EnsureTlsSetup();
auto client_max = MakeTlsFilter<TlsRecordMaximum>(client_); auto client_max = MakeTlsFilter<TlsRecordMaximum>(client_);
client_max->EnableDecryption();
auto server_max = MakeTlsFilter<TlsRecordMaximum>(server_); auto server_max = MakeTlsFilter<TlsRecordMaximum>(server_);
server_max->EnableDecryption(); if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
client_max->EnableDecryption();
server_max->EnableDecryption();
}
Connect(); Connect();
client_->SendData(send_size, send_size); client_->SendData(send_size, send_size);
@ -140,7 +142,9 @@ TEST_P(TlsConnectGeneric, RecordSizeMaximum) {
TEST_P(TlsConnectGeneric, RecordSizeMinimumClient) { TEST_P(TlsConnectGeneric, RecordSizeMinimumClient) {
EnsureTlsSetup(); EnsureTlsSetup();
auto server_max = MakeTlsFilter<TlsRecordMaximum>(server_); auto server_max = MakeTlsFilter<TlsRecordMaximum>(server_);
server_max->EnableDecryption(); if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
server_max->EnableDecryption();
}
client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64); client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
Connect(); Connect();
@ -152,7 +156,9 @@ TEST_P(TlsConnectGeneric, RecordSizeMinimumClient) {
TEST_P(TlsConnectGeneric, RecordSizeMinimumServer) { TEST_P(TlsConnectGeneric, RecordSizeMinimumServer) {
EnsureTlsSetup(); EnsureTlsSetup();
auto client_max = MakeTlsFilter<TlsRecordMaximum>(client_); auto client_max = MakeTlsFilter<TlsRecordMaximum>(client_);
client_max->EnableDecryption(); if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
client_max->EnableDecryption();
}
server_->SetOption(SSL_RECORD_SIZE_LIMIT, 64); server_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
Connect(); Connect();
@ -164,9 +170,11 @@ TEST_P(TlsConnectGeneric, RecordSizeMinimumServer) {
TEST_P(TlsConnectGeneric, RecordSizeAsymmetric) { TEST_P(TlsConnectGeneric, RecordSizeAsymmetric) {
EnsureTlsSetup(); EnsureTlsSetup();
auto client_max = MakeTlsFilter<TlsRecordMaximum>(client_); auto client_max = MakeTlsFilter<TlsRecordMaximum>(client_);
client_max->EnableDecryption();
auto server_max = MakeTlsFilter<TlsRecordMaximum>(server_); auto server_max = MakeTlsFilter<TlsRecordMaximum>(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); client_->SetOption(SSL_RECORD_SIZE_LIMIT, 64);
server_->SetOption(SSL_RECORD_SIZE_LIMIT, 100); server_->SetOption(SSL_RECORD_SIZE_LIMIT, 100);
@ -256,9 +264,11 @@ class TlsRecordPadder : public TlsRecordFilter {
return KEEP; return KEEP;
} }
uint16_t protection_epoch;
uint8_t inner_content_type; uint8_t inner_content_type;
DataBuffer plaintext; DataBuffer plaintext;
if (!Unprotect(header, record, &inner_content_type, &plaintext)) { if (!Unprotect(header, record, &protection_epoch, &inner_content_type,
&plaintext)) {
return KEEP; return KEEP;
} }
@ -267,8 +277,8 @@ class TlsRecordPadder : public TlsRecordFilter {
} }
DataBuffer ciphertext; DataBuffer ciphertext;
bool ok = bool ok = Protect(spec(protection_epoch), header, inner_content_type,
Protect(header, inner_content_type, plaintext, &ciphertext, padding_); plaintext, &ciphertext, padding_);
EXPECT_TRUE(ok); EXPECT_TRUE(ok);
if (!ok) { if (!ok) {
return KEEP; return KEEP;
@ -334,7 +344,9 @@ TEST_P(TlsConnectGeneric, RecordSizeCapExtensionClient) {
client_->SetOption(SSL_RECORD_SIZE_LIMIT, 16385); client_->SetOption(SSL_RECORD_SIZE_LIMIT, 16385);
auto capture = auto capture =
MakeTlsFilter<TlsExtensionCapture>(client_, ssl_record_size_limit_xtn); MakeTlsFilter<TlsExtensionCapture>(client_, ssl_record_size_limit_xtn);
capture->EnableDecryption(); if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
capture->EnableDecryption();
}
Connect(); Connect();
uint64_t val = 0; uint64_t val = 0;
@ -352,7 +364,9 @@ TEST_P(TlsConnectGeneric, RecordSizeCapExtensionServer) {
server_->SetOption(SSL_RECORD_SIZE_LIMIT, 16385); server_->SetOption(SSL_RECORD_SIZE_LIMIT, 16385);
auto capture = auto capture =
MakeTlsFilter<TlsExtensionCapture>(server_, ssl_record_size_limit_xtn); MakeTlsFilter<TlsExtensionCapture>(server_, ssl_record_size_limit_xtn);
capture->EnableDecryption(); if (version_ >= SSL_LIBRARY_VERSION_TLS_1_3) {
capture->EnableDecryption();
}
Connect(); Connect();
uint64_t val = 0; uint64_t val = 0;
@ -393,7 +407,9 @@ TEST_P(TlsConnectGeneric, RecordSizeServerExtensionInvalid) {
static const uint8_t v[] = {0xf4, 0x1f}; static const uint8_t v[] = {0xf4, 0x1f};
auto replace = MakeTlsFilter<TlsExtensionReplacer>( auto replace = MakeTlsFilter<TlsExtensionReplacer>(
server_, ssl_record_size_limit_xtn, DataBuffer(v, sizeof(v))); 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); ConnectExpectAlert(client_, kTlsAlertIllegalParameter);
} }
@ -403,7 +419,9 @@ TEST_P(TlsConnectGeneric, RecordSizeServerExtensionExtra) {
static const uint8_t v[] = {0x01, 0x00, 0x00}; static const uint8_t v[] = {0x01, 0x00, 0x00};
auto replace = MakeTlsFilter<TlsExtensionReplacer>( auto replace = MakeTlsFilter<TlsExtensionReplacer>(
server_, ssl_record_size_limit_xtn, DataBuffer(v, sizeof(v))); 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); ConnectExpectAlert(client_, kTlsAlertDecodeError);
} }

View file

@ -25,10 +25,13 @@ namespace nss_test {
if (g_ssl_gtest_verbose) LOG(a); \ if (g_ssl_gtest_verbose) LOG(a); \
} while (false) } while (false)
PRDescIdentity DummyPrSocket::LayerId() {
static PRDescIdentity id = PR_GetUniqueIdentity("dummysocket");
return id;
}
ScopedPRFileDesc DummyPrSocket::CreateFD() { ScopedPRFileDesc DummyPrSocket::CreateFD() {
static PRDescIdentity test_fd_identity = return DummyIOLayerMethods::CreateFD(DummyPrSocket::LayerId(), this);
PR_GetUniqueIdentity("testtransportadapter");
return DummyIOLayerMethods::CreateFD(test_fd_identity, this);
} }
void DummyPrSocket::Reset() { void DummyPrSocket::Reset() {
@ -136,19 +139,18 @@ int32_t DummyPrSocket::Write(PRFileDesc *f, const void *buf, int32_t length) {
DataBuffer filtered; DataBuffer filtered;
PacketFilter::Action action = PacketFilter::KEEP; PacketFilter::Action action = PacketFilter::KEEP;
if (filter_) { if (filter_) {
LOGV("Original packet: " << packet);
action = filter_->Process(packet, &filtered); action = filter_->Process(packet, &filtered);
} }
switch (action) { switch (action) {
case PacketFilter::CHANGE: case PacketFilter::CHANGE:
LOG("Original packet: " << packet);
LOG("Filtered packet: " << filtered); LOG("Filtered packet: " << filtered);
dst->PacketReceived(filtered); dst->PacketReceived(filtered);
break; break;
case PacketFilter::DROP: case PacketFilter::DROP:
LOG("Droppped packet: " << packet); LOG("Drop packet");
break; break;
case PacketFilter::KEEP: case PacketFilter::KEEP:
LOGV("Packet: " << packet);
dst->PacketReceived(packet); dst->PacketReceived(packet);
break; break;
} }

View file

@ -68,6 +68,8 @@ class DummyPrSocket : public DummyIOLayerMethods {
write_error_(0) {} write_error_(0) {}
virtual ~DummyPrSocket() {} virtual ~DummyPrSocket() {}
static PRDescIdentity LayerId();
// Create a file descriptor that will reference this object. The fd must not // Create a file descriptor that will reference this object. The fd must not
// live longer than this adapter; call PR_Close() before. // live longer than this adapter; call PR_Close() before.
ScopedPRFileDesc CreateFD(); ScopedPRFileDesc CreateFD();

View file

@ -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() { void TlsAgent::EnableSrtp() {
EXPECT_TRUE(EnsureTlsSetup()); EXPECT_TRUE(EnsureTlsSetup());
const uint16_t ciphers[] = {SRTP_AES128_CM_HMAC_SHA1_80, const uint16_t ciphers[] = {SRTP_AES128_CM_HMAC_SHA1_80,

View file

@ -139,6 +139,7 @@ class TlsAgent : public PollTarget {
const std::string& expected = "") const; const std::string& expected = "") const;
void EnableSrtp(); void EnableSrtp();
void CheckSrtp() const; void CheckSrtp() const;
void CheckEpochs(uint16_t expected_read, uint16_t expected_write) const;
void CheckErrorCode(int32_t expected) const; void CheckErrorCode(int32_t expected) const;
void WaitForErrorCode(int32_t expected, uint32_t delay) const; void WaitForErrorCode(int32_t expected, uint32_t delay) const;
// Send data on the socket, encrypting it. // Send data on the socket, encrypting it.

View file

@ -167,18 +167,8 @@ void TlsConnectTestBase::CheckShares(
void TlsConnectTestBase::CheckEpochs(uint16_t client_epoch, void TlsConnectTestBase::CheckEpochs(uint16_t client_epoch,
uint16_t server_epoch) const { uint16_t server_epoch) const {
uint16_t read_epoch = 0; client_->CheckEpochs(server_epoch, client_epoch);
uint16_t write_epoch = 0; server_->CheckEpochs(client_epoch, server_epoch);
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";
} }
void TlsConnectTestBase::ClearStats() { void TlsConnectTestBase::ClearStats() {

View file

@ -45,40 +45,65 @@ void TlsVersioned::WriteStream(std::ostream& stream) const {
} }
} }
void TlsRecordFilter::EnableDecryption() { TlsRecordFilter::TlsRecordFilter(const std::shared_ptr<TlsAgent>& a)
SSLInt_SetCipherSpecChangeFunc(agent()->ssl_fd(), CipherSpecChanged, : agent_(a) {
(void*)this); cipher_specs_.emplace_back(a->variant() == ssl_variant_datagram, 0);
} }
void TlsRecordFilter::CipherSpecChanged(void* arg, PRBool sending, void TlsRecordFilter::EnableDecryption() {
ssl3CipherSpec* newSpec) { EXPECT_EQ(SECSuccess,
TlsRecordFilter* self = static_cast<TlsRecordFilter*>(arg); SSL_SecretCallback(agent()->ssl_fd(), SecretCallback, this));
PRBool isServer = self->agent()->role() == TlsAgent::SERVER; decrypting_ = true;
}
void TlsRecordFilter::SecretCallback(PRFileDesc* fd, PRUint16 epoch,
SSLSecretDirection dir, PK11SymKey* secret,
void* arg) {
TlsRecordFilter* self = static_cast<TlsRecordFilter*>(arg);
if (g_ssl_gtest_verbose) { if (g_ssl_gtest_verbose) {
std::cerr << (isServer ? "server" : "client") << ": " std::cerr << self->agent()->role_str() << ": " << dir
<< (sending ? "send" : "receive") << " secret changed for epoch " << epoch << std::endl;
<< " cipher spec changed: " << newSpec->epoch << " ("
<< newSpec->phase << ")" << std::endl;
} }
if (!sending) {
if (dir == ssl_secret_read) {
return; return;
} }
uint64_t seq_no; for (auto& spec : self->cipher_specs_) {
if (self->agent()->variant() == ssl_variant_datagram) { ASSERT_NE(spec.epoch(), epoch) << "duplicate spec for epoch " << epoch;
seq_no = static_cast<uint64_t>(SSLInt_CipherSpecToEpoch(newSpec)) << 48;
} else {
seq_no = 0;
} }
self->in_sequence_number_ = seq_no;
self->out_sequence_number_ = seq_no; SSLPreliminaryChannelInfo preinfo;
self->dropped_record_ = false; EXPECT_EQ(SECSuccess,
self->cipher_spec_.reset(new TlsCipherSpec()); SSL_GetPreliminaryChannelInfo(self->agent()->ssl_fd(), &preinfo,
bool ret = self->cipher_spec_->Init( sizeof(preinfo)));
SSLInt_CipherSpecToEpoch(newSpec), SSLInt_CipherSpecToAlgorithm(newSpec), EXPECT_EQ(sizeof(preinfo), preinfo.length);
SSLInt_CipherSpecToKey(newSpec), SSLInt_CipherSpecToIv(newSpec));
EXPECT_EQ(true, ret); // 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 { bool TlsRecordFilter::is_dtls13() const {
@ -95,6 +120,23 @@ bool TlsRecordFilter::is_dtls13() const {
info.canSendEarlyData; 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, PacketFilter::Action TlsRecordFilter::Filter(const DataBuffer& input,
DataBuffer* output) { DataBuffer* output) {
// Disable during shutdown. // Disable during shutdown.
@ -108,34 +150,28 @@ PacketFilter::Action TlsRecordFilter::Filter(const DataBuffer& input,
output->Allocate(input.len()); output->Allocate(input.len());
TlsParser parser(input); 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<uint64_t>(write_epoch) << 48;
while (parser.remaining()) { while (parser.remaining()) {
TlsRecordHeader header; TlsRecordHeader header;
DataBuffer record; DataBuffer record;
if (!header.Parse(is_dtls13(), guess_seqno, &parser, &record)) {
if (!header.Parse(is_dtls13(), in_sequence_number_, &parser, &record)) {
ADD_FAILURE() << "not a valid record"; ADD_FAILURE() << "not a valid record";
return KEEP; 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) { if (FilterRecord(header, record, &offset, output) != KEEP) {
changed = true; changed = true;
} else { } else {
@ -159,14 +195,16 @@ PacketFilter::Action TlsRecordFilter::FilterRecord(
DataBuffer filtered; DataBuffer filtered;
uint8_t inner_content_type; uint8_t inner_content_type;
DataBuffer plaintext; DataBuffer plaintext;
uint16_t protection_epoch = 0;
if (!Unprotect(header, record, &inner_content_type, &plaintext)) { if (!Unprotect(header, record, &protection_epoch, &inner_content_type,
if (g_ssl_gtest_verbose) { &plaintext)) {
std::cerr << "unprotect failed: " << header << ":" << record << std::endl; std::cerr << agent()->role_str() << ": unprotect failed: " << header << ":"
} << record << std::endl;
return KEEP; return KEEP;
} }
auto& protection_spec = spec(protection_epoch);
TlsRecordHeader real_header(header.variant(), header.version(), TlsRecordHeader real_header(header.variant(), header.version(),
inner_content_type, header.sequence_number()); 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 // In stream mode, even if something doesn't change we need to re-encrypt if
// previous packets were dropped. // previous packets were dropped.
if (action == KEEP) { 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; return KEEP;
} }
filtered = plaintext; filtered = plaintext;
@ -182,7 +222,7 @@ PacketFilter::Action TlsRecordFilter::FilterRecord(
if (action == DROP) { if (action == DROP) {
std::cerr << "record drop: " << header << ":" << record << std::endl; std::cerr << "record drop: " << header << ":" << record << std::endl;
dropped_record_ = true; protection_spec.RecordDropped();
return DROP; return DROP;
} }
@ -192,19 +232,18 @@ PacketFilter::Action TlsRecordFilter::FilterRecord(
std::cerr << "record new: " << filtered << std::endl; std::cerr << "record new: " << filtered << std::endl;
} }
uint64_t seq_num; uint64_t seq_num = protection_spec.next_out_seqno();
if (header.is_dtls() || !cipher_spec_ || if (!decrypting_ && header.is_dtls()) {
header.content_type() != ssl_ct_application_data) { // Copy over the epoch, which isn't tracked when not decrypting.
seq_num = header.sequence_number(); seq_num |= header.sequence_number() & (0xffffULL << 48);
} else {
seq_num = out_sequence_number_++;
} }
TlsRecordHeader out_header(header.variant(), header.version(), TlsRecordHeader out_header(header.variant(), header.version(),
header.content_type(), seq_num); header.content_type(), seq_num);
DataBuffer ciphertext; DataBuffer ciphertext;
bool rv = Protect(out_header, inner_content_type, filtered, &ciphertext); bool rv = Protect(protection_spec, out_header, inner_content_type, filtered,
EXPECT_TRUE(rv); &ciphertext);
if (!rv) { if (!rv) {
return KEEP; return KEEP;
} }
@ -227,15 +266,20 @@ uint64_t TlsRecordHeader::RecoverSequenceNumber(uint64_t expected,
uint32_t partial, uint32_t partial,
size_t partial_bits) { size_t partial_bits) {
EXPECT_GE(32U, 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 // First we determine the highest possible value. This is half the
// expressible range above the expected value. // expressible range above the expected value, less 1.
uint64_t cap = expected + (1ULL << (partial_bits - 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. // Add the partial piece in. e.g., xxxx789a and 1234 becomes xxxx1234.
uint64_t seq_no = (cap & ~mask) | partial; uint64_t seq_no = (cap & ~mask) | partial;
// If the partial value is higher than the same partial piece from the cap, // 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. // 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; seq_no -= 1ULL << partial_bits;
} }
return seq_no; return seq_no;
@ -375,16 +419,41 @@ size_t TlsRecordHeader::Write(DataBuffer* buffer, size_t offset,
bool TlsRecordFilter::Unprotect(const TlsRecordHeader& header, bool TlsRecordFilter::Unprotect(const TlsRecordHeader& header,
const DataBuffer& ciphertext, const DataBuffer& ciphertext,
uint16_t* protection_epoch,
uint8_t* inner_content_type, uint8_t* inner_content_type,
DataBuffer* plaintext) { 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<uint16_t>(header.sequence_number() >> 48);
}
spec(ep).RecordUnprotected(header.sequence_number());
*protection_epoch = ep;
*inner_content_type = header.content_type(); *inner_content_type = header.content_type();
*plaintext = ciphertext; *plaintext = ciphertext;
return true; return true;
} }
if (!cipher_spec_->Unprotect(header, ciphertext, plaintext)) { uint16_t ep = 0;
return false; if (agent()->variant() == ssl_variant_datagram) {
ep = static_cast<uint16_t>(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(); size_t len = plaintext->len();
@ -396,33 +465,45 @@ bool TlsRecordFilter::Unprotect(const TlsRecordHeader& header,
return false; return false;
} }
*protection_epoch = ep;
*inner_content_type = plaintext->data()[len - 1]; *inner_content_type = plaintext->data()[len - 1];
plaintext->Truncate(len - 1); plaintext->Truncate(len - 1);
if (g_ssl_gtest_verbose) { if (g_ssl_gtest_verbose) {
std::cerr << "unprotect: " << std::hex << header.sequence_number() std::cerr << agent()->role_str() << ": unprotect: epoch=" << ep
<< std::dec << " type=" << static_cast<int>(*inner_content_type) << " seq=" << std::hex << header.sequence_number() << std::dec
<< " " << *plaintext << std::endl; << " " << *plaintext << std::endl;
} }
return true; return true;
} }
bool TlsRecordFilter::Protect(const TlsRecordHeader& header, bool TlsRecordFilter::Protect(TlsCipherSpec& protection_spec,
const TlsRecordHeader& header,
uint8_t inner_content_type, uint8_t inner_content_type,
const DataBuffer& plaintext, const DataBuffer& plaintext,
DataBuffer* ciphertext, size_t padding) { 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; *ciphertext = plaintext;
return true; return true;
} }
if (g_ssl_gtest_verbose) {
std::cerr << "protect: " << header.sequence_number() << std::endl;
}
DataBuffer padded; DataBuffer padded;
padded.Allocate(plaintext.len() + 1 + padding); padded.Allocate(plaintext.len() + 1 + padding);
size_t offset = padded.Write(0, plaintext.data(), plaintext.len()); size_t offset = padded.Write(0, plaintext.data(), plaintext.len());
padded.Write(offset, inner_content_type, 1); 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) { bool IsHelloRetry(const DataBuffer& body) {

View file

@ -97,13 +97,7 @@ inline std::shared_ptr<T> MakeTlsFilter(const std::shared_ptr<TlsAgent>& agent,
// Abstract filter that operates on entire (D)TLS records. // Abstract filter that operates on entire (D)TLS records.
class TlsRecordFilter : public PacketFilter { class TlsRecordFilter : public PacketFilter {
public: public:
TlsRecordFilter(const std::shared_ptr<TlsAgent>& a) TlsRecordFilter(const std::shared_ptr<TlsAgent>& a);
: agent_(a),
count_(0),
cipher_spec_(),
dropped_record_(false),
in_sequence_number_(0),
out_sequence_number_(0) {}
std::shared_ptr<TlsAgent> agent() const { return agent_.lock(); } std::shared_ptr<TlsAgent> agent() const { return agent_.lock(); }
@ -118,10 +112,11 @@ class TlsRecordFilter : public PacketFilter {
// behavior. // behavior.
void EnableDecryption(); void EnableDecryption();
bool Unprotect(const TlsRecordHeader& header, const DataBuffer& cipherText, bool Unprotect(const TlsRecordHeader& header, const DataBuffer& cipherText,
uint8_t* inner_content_type, DataBuffer* plaintext); uint16_t* protection_epoch, uint8_t* inner_content_type,
bool Protect(const TlsRecordHeader& header, uint8_t inner_content_type, DataBuffer* plaintext);
const DataBuffer& plaintext, DataBuffer* ciphertext, bool Protect(TlsCipherSpec& protection_spec, const TlsRecordHeader& header,
size_t padding = 0); uint8_t inner_content_type, const DataBuffer& plaintext,
DataBuffer* ciphertext, size_t padding = 0);
protected: protected:
// There are two filter functions which can be overriden. Both are // There are two filter functions which can be overriden. Both are
@ -146,20 +141,17 @@ class TlsRecordFilter : public PacketFilter {
} }
bool is_dtls13() const; bool is_dtls13() const;
TlsCipherSpec& spec(uint16_t epoch);
private: private:
static void CipherSpecChanged(void* arg, PRBool sending, static void SecretCallback(PRFileDesc* fd, PRUint16 epoch,
ssl3CipherSpec* newSpec); SSLSecretDirection dir, PK11SymKey* secret,
void* arg);
std::weak_ptr<TlsAgent> agent_; std::weak_ptr<TlsAgent> agent_;
size_t count_; size_t count_ = 0;
std::unique_ptr<TlsCipherSpec> cipher_spec_; std::vector<TlsCipherSpec> cipher_specs_;
// Whether we dropped a record since the cipher spec changed. bool decrypting_ = false;
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_;
}; };
inline std::ostream& operator<<(std::ostream& stream, const TlsVersioned& v) { inline std::ostream& operator<<(std::ostream& stream, const TlsVersioned& v) {

View file

@ -7,6 +7,9 @@
#include "tls_protect.h" #include "tls_protect.h"
#include "tls_filter.h" #include "tls_filter.h"
// Do this to avoid having to re-implement HKDF.
#include "tls13hkdf.h"
namespace nss_test { namespace nss_test {
AeadCipher::~AeadCipher() { AeadCipher::~AeadCipher() {
@ -15,32 +18,37 @@ AeadCipher::~AeadCipher() {
} }
} }
bool AeadCipher::Init(PK11SymKey *key, const uint8_t *iv) { bool AeadCipher::Init(PK11SymKey* key, const uint8_t* iv) {
key_ = PK11_ReferenceSymKey(key); key_ = key;
if (!key_) return false; if (!key_) return false;
memcpy(iv_, iv, sizeof(iv_)); 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; return true;
} }
void AeadCipher::FormatNonce(uint64_t seq, uint8_t *nonce) { void AeadCipher::FormatNonce(uint64_t seq, uint8_t* nonce) {
memcpy(nonce, iv_, 12); memcpy(nonce, iv_, 12);
for (size_t i = 0; i < 8; ++i) { for (size_t i = 0; i < 8; ++i) {
nonce[12 - (i + 1)] ^= seq & 0xff; nonce[12 - (i + 1)] ^= seq & 0xff;
seq >>= 8; seq >>= 8;
} }
DataBuffer d(nonce, 12);
} }
bool AeadCipher::AeadInner(bool decrypt, void *params, size_t param_length, bool AeadCipher::AeadInner(bool decrypt, void* params, size_t param_length,
const uint8_t *in, size_t inlen, uint8_t *out, const uint8_t* in, size_t inlen, uint8_t* out,
size_t *outlen, size_t maxlen) { size_t* outlen, size_t maxlen) {
SECStatus rv; SECStatus rv;
unsigned int uoutlen = 0; unsigned int uoutlen = 0;
SECItem param = { SECItem param = {
siBuffer, static_cast<unsigned char *>(params), siBuffer, static_cast<unsigned char*>(params),
static_cast<unsigned int>(param_length), static_cast<unsigned int>(param_length),
}; };
@ -54,28 +62,28 @@ bool AeadCipher::AeadInner(bool decrypt, void *params, size_t param_length,
return rv == SECSuccess; return rv == SECSuccess;
} }
bool AeadCipherAesGcm::Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len, bool AeadCipherAesGcm::Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len,
uint64_t seq, const uint8_t *in, size_t inlen, uint64_t seq, const uint8_t* in, size_t inlen,
uint8_t *out, size_t *outlen, size_t maxlen) { uint8_t* out, size_t* outlen, size_t maxlen) {
CK_GCM_PARAMS aeadParams; CK_GCM_PARAMS aeadParams;
unsigned char nonce[12]; unsigned char nonce[12];
memset(&aeadParams, 0, sizeof(aeadParams)); memset(&aeadParams, 0, sizeof(aeadParams));
aeadParams.pIv = nonce; aeadParams.pIv = nonce;
aeadParams.ulIvLen = sizeof(nonce); aeadParams.ulIvLen = sizeof(nonce);
aeadParams.pAAD = const_cast<uint8_t *>(hdr); aeadParams.pAAD = const_cast<uint8_t*>(hdr);
aeadParams.ulAADLen = hdr_len; aeadParams.ulAADLen = hdr_len;
aeadParams.ulTagBits = 128; aeadParams.ulTagBits = 128;
FormatNonce(seq, nonce); FormatNonce(seq, nonce);
return AeadInner(decrypt, (unsigned char *)&aeadParams, sizeof(aeadParams), return AeadInner(decrypt, (unsigned char*)&aeadParams, sizeof(aeadParams), in,
in, inlen, out, outlen, maxlen); 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, size_t hdr_len, uint64_t seq,
const uint8_t *in, size_t inlen, const uint8_t* in, size_t inlen,
uint8_t *out, size_t *outlen, uint8_t* out, size_t* outlen,
size_t maxlen) { size_t maxlen) {
CK_NSS_AEAD_PARAMS aeadParams; CK_NSS_AEAD_PARAMS aeadParams;
unsigned char nonce[12]; unsigned char nonce[12];
@ -83,67 +91,126 @@ bool AeadCipherChacha20Poly1305::Aead(bool decrypt, const uint8_t *hdr,
memset(&aeadParams, 0, sizeof(aeadParams)); memset(&aeadParams, 0, sizeof(aeadParams));
aeadParams.pNonce = nonce; aeadParams.pNonce = nonce;
aeadParams.ulNonceLen = sizeof(nonce); aeadParams.ulNonceLen = sizeof(nonce);
aeadParams.pAAD = const_cast<uint8_t *>(hdr); aeadParams.pAAD = const_cast<uint8_t*>(hdr);
aeadParams.ulAADLen = hdr_len; aeadParams.ulAADLen = hdr_len;
aeadParams.ulTagLen = 16; aeadParams.ulTagLen = 16;
FormatNonce(seq, nonce); FormatNonce(seq, nonce);
return AeadInner(decrypt, (unsigned char *)&aeadParams, sizeof(aeadParams), return AeadInner(decrypt, (unsigned char*)&aeadParams, sizeof(aeadParams), in,
in, inlen, out, outlen, maxlen); inlen, out, outlen, maxlen);
} }
bool TlsCipherSpec::Init(uint16_t epoc, SSLCipherAlgorithm cipher, static uint64_t FirstSeqno(bool dtls, uint16_t epoc) {
PK11SymKey *key, const uint8_t *iv) { if (dtls) {
epoch_ = epoc; return static_cast<uint64_t>(epoc) << 48;
switch (cipher) { }
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: case ssl_calg_aes_gcm:
aead_.reset(new AeadCipherAesGcm()); aead_.reset(new AeadCipherAesGcm());
mech = CKM_AES_GCM;
break; break;
case ssl_calg_chacha20: case ssl_calg_chacha20:
aead_.reset(new AeadCipherChacha20Poly1305()); aead_.reset(new AeadCipherChacha20Poly1305());
mech = CKM_NSS_CHACHA20_POLY1305;
break; break;
default: default:
return false; 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); return aead_->Init(key, iv);
} }
bool TlsCipherSpec::Unprotect(const TlsRecordHeader &header, bool TlsCipherSpec::Unprotect(const TlsRecordHeader& header,
const DataBuffer &ciphertext, const DataBuffer& ciphertext,
DataBuffer *plaintext) { DataBuffer* plaintext) {
if (aead_ == nullptr) {
return false;
}
// Make space. // Make space.
plaintext->Allocate(ciphertext.len()); plaintext->Allocate(ciphertext.len());
auto header_bytes = header.header(); auto header_bytes = header.header();
size_t len; size_t len;
bool ret = uint64_t seqno;
aead_->Aead(true, header_bytes.data(), header_bytes.len(), if (dtls_) {
header.sequence_number(), ciphertext.data(), ciphertext.len(), seqno = header.sequence_number();
plaintext->data(), &len, plaintext->len()); } else {
if (!ret) return false; 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); plaintext->Truncate(len);
return true; return true;
} }
bool TlsCipherSpec::Protect(const TlsRecordHeader &header, bool TlsCipherSpec::Protect(const TlsRecordHeader& header,
const DataBuffer &plaintext, const DataBuffer& plaintext,
DataBuffer *ciphertext) { DataBuffer* ciphertext) {
if (aead_ == nullptr) {
return false;
}
// Make a padded buffer. // Make a padded buffer.
ciphertext->Allocate(plaintext.len() + ciphertext->Allocate(plaintext.len() +
32); // Room for any plausible auth tag 32); // Room for any plausible auth tag
size_t len; size_t len;
DataBuffer header_bytes; DataBuffer header_bytes;
(void)header.WriteHeader(&header_bytes, 0, plaintext.len() + 16); (void)header.WriteHeader(&header_bytes, 0, plaintext.len() + 16);
bool ret = uint64_t seqno;
aead_->Aead(false, header_bytes.data(), header_bytes.len(), if (dtls_) {
header.sequence_number(), plaintext.data(), plaintext.len(), seqno = header.sequence_number();
ciphertext->data(), &len, ciphertext->len()); } else {
if (!ret) return false; 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); ciphertext->Truncate(len);
return true; return true;

View file

@ -22,19 +22,19 @@ class AeadCipher {
AeadCipher(CK_MECHANISM_TYPE mech) : mech_(mech), key_(nullptr) {} AeadCipher(CK_MECHANISM_TYPE mech) : mech_(mech), key_(nullptr) {}
virtual ~AeadCipher(); virtual ~AeadCipher();
bool Init(PK11SymKey *key, const uint8_t *iv); bool Init(PK11SymKey* key, const uint8_t* iv);
virtual bool Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len, 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, uint64_t seq, const uint8_t* in, size_t inlen, uint8_t* out,
size_t *outlen, size_t maxlen) = 0; size_t* outlen, size_t maxlen) = 0;
protected: protected:
void FormatNonce(uint64_t seq, uint8_t *nonce); void FormatNonce(uint64_t seq, uint8_t* nonce);
bool AeadInner(bool decrypt, void *params, size_t param_length, bool AeadInner(bool decrypt, void* params, size_t param_length,
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); size_t maxlen);
CK_MECHANISM_TYPE mech_; CK_MECHANISM_TYPE mech_;
PK11SymKey *key_; PK11SymKey* key_;
uint8_t iv_[12]; uint8_t iv_[12];
}; };
@ -43,8 +43,8 @@ class AeadCipherChacha20Poly1305 : public AeadCipher {
AeadCipherChacha20Poly1305() : AeadCipher(CKM_NSS_CHACHA20_POLY1305) {} AeadCipherChacha20Poly1305() : AeadCipher(CKM_NSS_CHACHA20_POLY1305) {}
protected: protected:
bool Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len, uint64_t seq, 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, const uint8_t* in, size_t inlen, uint8_t* out, size_t* outlen,
size_t maxlen); size_t maxlen);
}; };
@ -53,27 +53,42 @@ class AeadCipherAesGcm : public AeadCipher {
AeadCipherAesGcm() : AeadCipher(CKM_AES_GCM) {} AeadCipherAesGcm() : AeadCipher(CKM_AES_GCM) {}
protected: protected:
bool Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len, uint64_t seq, 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, const uint8_t* in, size_t inlen, uint8_t* out, size_t* outlen,
size_t maxlen); size_t maxlen);
}; };
// Our analog of ssl3CipherSpec // Our analog of ssl3CipherSpec
class TlsCipherSpec { class TlsCipherSpec {
public: 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, bool Protect(const TlsRecordHeader& header, const DataBuffer& plaintext,
const uint8_t *iv); 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_; } 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: private:
bool dtls_;
uint16_t epoch_; uint16_t epoch_;
uint64_t in_seqno_;
uint64_t out_seqno_;
bool record_dropped_ = false;
std::unique_ptr<AeadCipher> aead_; std::unique_ptr<AeadCipher> aead_;
}; };

View file

@ -1677,6 +1677,92 @@ PK11_MakeKEAPubKey(unsigned char *keyData, int length)
return pubk; 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 * 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 * 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, nickname, publicValue, isPerm, isPrivate,
key_type, usage, usageCount, wincx); key_type, usage, usageCount, wincx);
if (privKey) { if (privKey) {
if (privk) {
*privk = privKey;
} else {
SECKEY_DestroyPrivateKey(privKey);
}
privKey = NULL;
rv = SECSuccess; rv = SECSuccess;
goto done; goto done;
} }
@ -1837,6 +1917,25 @@ try_faulty_3des:
rv = SECFailure; rv = SECFailure;
done: 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) { if (crypto_param != NULL) {
SECITEM_ZfreeItem(crypto_param, PR_TRUE); SECITEM_ZfreeItem(crypto_param, PR_TRUE);
} }

View file

@ -1815,8 +1815,6 @@ sftk_GetPubKey(SFTKObject *object, CK_KEY_TYPE key_type,
break; /* key was not DER encoded, no need to unwrap */ break; /* key was not DER encoded, no need to unwrap */
} }
PORT_Assert(pubKey->u.ec.ecParams.name != ECCurve25519);
/* handle the encoded case */ /* handle the encoded case */
if ((pubKey->u.ec.publicValue.data[0] == SEC_ASN1_OCTET_STRING) && if ((pubKey->u.ec.publicValue.data[0] == SEC_ASN1_OCTET_STRING) &&
pubKey->u.ec.publicValue.len > keyLen) { pubKey->u.ec.publicValue.len > keyLen) {
@ -1827,7 +1825,13 @@ sftk_GetPubKey(SFTKObject *object, CK_KEY_TYPE key_type,
SEC_ASN1_GET(SEC_OctetStringTemplate), SEC_ASN1_GET(SEC_OctetStringTemplate),
&pubKey->u.ec.publicValue); &pubKey->u.ec.publicValue);
/* nope, didn't decode correctly */ /* 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; crv = CKR_ATTRIBUTE_VALUE_INVALID;
break; break;
} }

View file

@ -482,7 +482,7 @@ dtls13_HandleAck(sslSocket *ss, sslBuffer *databuf)
* for the holddown period to process retransmitted Finisheds. * for the holddown period to process retransmitted Finisheds.
*/ */
if (!ss->sec.isServer && (ss->ssl3.hs.ws == idle_handshake)) { if (!ss->sec.isServer && (ss->ssl3.hs.ws == idle_handshake)) {
ssl_CipherSpecReleaseByEpoch(ss, CipherSpecRead, ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read,
TrafficKeyHandshake); TrafficKeyHandshake);
} }
} }
@ -509,6 +509,6 @@ dtls13_HolddownTimerCb(sslSocket *ss)
{ {
SSL_TRC(10, ("%d: SSL3[%d]: holddown timer fired", SSL_TRC(10, ("%d: SSL3[%d]: holddown timer fired",
SSL_GETPID(), ss->fd)); SSL_GETPID(), ss->fd));
ssl_CipherSpecReleaseByEpoch(ss, CipherSpecRead, TrafficKeyHandshake); ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read, TrafficKeyHandshake);
ssl_ClearPRCList(&ss->ssl3.hs.dtlsRcvdHandshake, NULL); ssl_ClearPRCList(&ss->ssl3.hs.dtlsRcvdHandshake, NULL);
} }

View file

@ -542,7 +542,6 @@ dtls_QueueMessage(sslSocket *ss, SSLContentType ct,
/* Add DTLS handshake message to the pending queue /* Add DTLS handshake message to the pending queue
* Empty the sendBuf buffer. * Empty the sendBuf buffer.
* This function returns SECSuccess or SECFailure, never SECWouldBlock.
* Always set sendBuf.len to 0, even when returning SECFailure. * Always set sendBuf.len to 0, even when returning SECFailure.
* *
* Called from: * Called from:

View file

@ -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. */ * This is disabled by default and will be removed in a future version. */
#define SSL_ENABLE_V2_COMPATIBLE_HELLO 38 #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 #ifdef SSL_DEPRECATED_FUNCTION
/* Old deprecated function names */ /* Old deprecated function names */
SSL_IMPORT SECStatus SSL_Enable(PRFileDesc *fd, int option, PRIntn on); SSL_IMPORT SECStatus SSL_Enable(PRFileDesc *fd, int option, PRIntn on);

View file

@ -1394,14 +1394,14 @@ loser:
} }
static SECStatus static SECStatus
ssl3_SetupPendingCipherSpec(sslSocket *ss, CipherSpecDirection direction, ssl3_SetupPendingCipherSpec(sslSocket *ss, SSLSecretDirection direction,
const ssl3CipherSuiteDef *suiteDef, const ssl3CipherSuiteDef *suiteDef,
ssl3CipherSpec **specp) ssl3CipherSpec **specp)
{ {
ssl3CipherSpec *spec; ssl3CipherSpec *spec;
const ssl3CipherSpec *prev; 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) { if (prev->epoch == PR_UINT16_MAX) {
PORT_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED); PORT_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED);
return SECFailure; return SECFailure;
@ -1417,7 +1417,7 @@ ssl3_SetupPendingCipherSpec(sslSocket *ss, CipherSpecDirection direction,
spec->epoch = prev->epoch + 1; spec->epoch = prev->epoch + 1;
spec->nextSeqNum = 0; spec->nextSeqNum = 0;
if (IS_DTLS(ss) && direction == CipherSpecRead) { if (IS_DTLS(ss) && direction == ssl_secret_read) {
dtls_InitRecvdRecords(&spec->recvdRecords); dtls_InitRecvdRecords(&spec->recvdRecords);
} }
ssl_SetSpecVersions(ss, spec); ssl_SetSpecVersions(ss, spec);
@ -1471,12 +1471,12 @@ ssl3_SetupBothPendingCipherSpecs(sslSocket *ss)
ss->ssl3.hs.kea_def = &kea_defs[kea]; ss->ssl3.hs.kea_def = &kea_defs[kea];
PORT_Assert(ss->ssl3.hs.kea_def->kea == 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); &ss->ssl3.prSpec);
if (rv != SECSuccess) { if (rv != SECSuccess) {
goto loser; goto loser;
} }
rv = ssl3_SetupPendingCipherSpec(ss, CipherSpecWrite, suiteDef, rv = ssl3_SetupPendingCipherSpec(ss, ssl_secret_write, suiteDef,
&ss->ssl3.pwSpec); &ss->ssl3.pwSpec);
if (rv != SECSuccess) { if (rv != SECSuccess) {
goto loser; goto loser;
@ -1727,7 +1727,7 @@ ssl3_InitPendingContexts(sslSocket *ss, ssl3CipherSpec *spec)
spec->cipher = (SSLCipher)PK11_CipherOp; spec->cipher = (SSLCipher)PK11_CipherOp;
encMechanism = ssl3_Alg2Mech(calg); 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 * build the context
@ -2215,7 +2215,7 @@ ssl_ProtectRecord(sslSocket *ss, ssl3CipherSpec *cwSpec, SSLContentType ct,
unsigned int lenOffset; unsigned int lenOffset;
SECStatus rv; SECStatus rv;
PORT_Assert(cwSpec->direction == CipherSpecWrite); PORT_Assert(cwSpec->direction == ssl_secret_write);
PORT_Assert(SSL_BUFFER_LEN(wrBuf) == 0); PORT_Assert(SSL_BUFFER_LEN(wrBuf) == 0);
PORT_Assert(cwSpec->cipherDef->max_records <= RECORD_SEQ_MAX); 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 * Returns the number of bytes of plaintext that were successfully sent
* plus the number of bytes of plaintext that were copied into the * plus the number of bytes of plaintext that were copied into the
* output (write) buffer. * output (write) buffer.
* Returns SECFailure on a hard IO error, memory error, or crypto error. * Returns -1 on an error. PR_WOULD_BLOCK_ERROR is set if the error is blocking
* Does NOT return SECWouldBlock. * and not terminal.
* *
* Notes on the use of the private ssl flags: * Notes on the use of the private ssl flags:
* (no private SSL flags) * (no private SSL flags)
@ -2360,13 +2360,26 @@ ssl3_SendRecord(sslSocket *ss,
* error, so don't overwrite. */ * error, so don't overwrite. */
PORT_SetError(SSL_ERROR_HANDSHAKE_FAILED); PORT_SetError(SSL_ERROR_HANDSHAKE_FAILED);
} }
return SECFailure; return -1;
} }
/* check for Token Presence */ /* check for Token Presence */
if (!ssl3_ClientAuthTokenPresent(ss->sec.ci.sid)) { if (!ssl3_ClientAuthTokenPresent(ss->sec.ci.sid)) {
PORT_SetError(SSL_ERROR_TOKEN_INSERTION_REMOVAL); 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) { if (cwSpec) {
@ -2470,7 +2483,7 @@ loser:
#define SSL3_PENDING_HIGH_WATER 1024 #define SSL3_PENDING_HIGH_WATER 1024
/* Attempt to send the content of "in" in an SSL application_data record. /* 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 int
ssl3_SendApplicationData(sslSocket *ss, const unsigned char *in, 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)); PORT_Assert(!(flags & ssl_SEND_FLAG_NO_RETRANSMIT));
if (len < 0 || !in) { if (len < 0 || !in) {
PORT_SetError(PR_INVALID_ARGUMENT_ERROR); PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
return SECFailure; return -1;
} }
if (ss->pendingBuf.len > SSL3_PENDING_HIGH_WATER && if (ss->pendingBuf.len > SSL3_PENDING_HIGH_WATER &&
!ssl_SocketIsBlocking(ss)) { !ssl_SocketIsBlocking(ss)) {
PORT_Assert(!ssl_SocketIsBlocking(ss)); PORT_Assert(!ssl_SocketIsBlocking(ss));
PORT_SetError(PR_WOULD_BLOCK_ERROR); PORT_SetError(PR_WOULD_BLOCK_ERROR);
return SECFailure; return -1;
} }
if (ss->appDataBuffered && len) { if (ss->appDataBuffered && len) {
PORT_Assert(in[0] == (unsigned char)(ss->appDataBuffered)); PORT_Assert(in[0] == (unsigned char)(ss->appDataBuffered));
if (in[0] != (unsigned char)(ss->appDataBuffered)) { if (in[0] != (unsigned char)(ss->appDataBuffered)) {
PORT_SetError(PR_INVALID_ARGUMENT_ERROR); PORT_SetError(PR_INVALID_ARGUMENT_ERROR);
return SECFailure; return -1;
} }
in++; in++;
len--; len--;
@ -2548,7 +2561,7 @@ ssl3_SendApplicationData(sslSocket *ss, const unsigned char *in,
PORT_Assert(ss->lastWriteBlocked); PORT_Assert(ss->lastWriteBlocked);
break; break;
} }
return SECFailure; /* error code set by ssl3_SendRecord */ return -1; /* error code set by ssl3_SendRecord */
} }
totalSent += sent; totalSent += sent;
if (ss->pendingBuf.len) { if (ss->pendingBuf.len) {
@ -2577,7 +2590,6 @@ ssl3_SendApplicationData(sslSocket *ss, const unsigned char *in,
} }
/* Attempt to send buffered handshake messages. /* Attempt to send buffered handshake messages.
* This function returns SECSuccess or SECFailure, never SECWouldBlock.
* Always set sendBuf.len to 0, even when returning SECFailure. * Always set sendBuf.len to 0, even when returning SECFailure.
* *
* Depending on whether we are doing DTLS or not, this either calls * 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. /* 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. * Always set sendBuf.len to 0, even when returning SECFailure.
* *
* Called from ssl3_FlushHandshake * Called from ssl3_FlushHandshake
@ -7382,6 +7393,9 @@ ssl3_CompleteHandleCertificateRequest(sslSocket *ss,
if (ss->getClientAuthData != NULL) { if (ss->getClientAuthData != NULL) {
PORT_Assert((ss->ssl3.hs.preliminaryInfo & ssl_preinfo_all) == PORT_Assert((ss->ssl3.hs.preliminaryInfo & ssl_preinfo_all) ==
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!! */ /* XXX Should pass cert_types and algorithms in this call!! */
rv = (SECStatus)(*ss->getClientAuthData)(ss->getClientAuthDataArg, rv = (SECStatus)(*ss->getClientAuthData)(ss->getClientAuthDataArg,
ss->fd, ca_list, ss->fd, ca_list,
@ -7603,7 +7617,8 @@ ssl3_SendClientSecondRound(sslSocket *ss)
" certificate authentication is still pending.", " certificate authentication is still pending.",
SSL_GETPID(), ss->fd)); SSL_GETPID(), ss->fd));
ss->ssl3.hs.restartTarget = ssl3_SendClientSecondRound; ss->ssl3.hs.restartTarget = ssl3_SendClientSecondRound;
return SECWouldBlock; PORT_SetError(PR_WOULD_BLOCK_ERROR);
return SECFailure;
} }
ssl_GetXmitBufLock(ss); /*******************************/ 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); ss->sec.ci.sid->peerCert = CERT_DupCertificate(ss->sec.peerCert);
if (!ss->sec.isServer) { if (!ss->sec.isServer) {
@ -10898,13 +10916,6 @@ ssl3_AuthCertificateComplete(sslSocket *ss, PRErrorCode error)
} }
rv = target(ss); 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 { } else {
SSL_TRC(3, ("%d: SSL3[%p]: certificate authentication won the race with" SSL_TRC(3, ("%d: SSL3[%p]: certificate authentication won the race with"
" peer's finished message", " peer's finished message",
@ -11445,7 +11456,8 @@ xmit_loser:
} }
ss->ssl3.hs.restartTarget = ssl3_FinishHandshake; ss->ssl3.hs.restartTarget = ssl3_FinishHandshake;
return SECWouldBlock; PORT_SetError(PR_WOULD_BLOCK_ERROR);
return SECFailure;
} }
rv = ssl3_FinishHandshake(ss); rv = ssl3_FinishHandshake(ss);
@ -11649,9 +11661,10 @@ ssl3_HandleHandshakeMessage(sslSocket *ss, PRUint8 *b, PRUint32 length,
* authenticate the certificate in ssl3_HandleCertificateStatus. * authenticate the certificate in ssl3_HandleCertificateStatus.
*/ */
rv = ssl3_AuthCertificate(ss); /* sets ss->ssl3.hs.ws */ rv = ssl3_AuthCertificate(ss); /* sets ss->ssl3.hs.ws */
PORT_Assert(rv != SECWouldBlock);
if (rv != SECSuccess) { 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 static SECStatus
ssl3_HandleHandshake(sslSocket *ss, sslBuffer *origBuf) ssl3_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
{ {
/* sslBuffer buf = *origBuf; /* Work from a copy. */
* 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 */
SECStatus rv; SECStatus rv;
PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss)); PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
if (buf->buf == NULL) { while (buf.len > 0) {
*buf = *origBuf;
}
while (buf->len > 0) {
if (ss->ssl3.hs.header_bytes < 4) { if (ss->ssl3.hs.header_bytes < 4) {
PRUint8 t; PRUint8 t;
t = *(buf->buf++); t = *(buf.buf++);
buf->len--; buf.len--;
if (ss->ssl3.hs.header_bytes++ == 0) if (ss->ssl3.hs.header_bytes++ == 0)
ss->ssl3.hs.msg_type = (SSLHandshakeType)t; ss->ssl3.hs.msg_type = (SSLHandshakeType)t;
else else
@ -11847,7 +11849,7 @@ ssl3_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
#undef MAX_HANDSHAKE_MSG_LEN #undef MAX_HANDSHAKE_MSG_LEN
/* If msg_len is zero, be sure we fall through, /* 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) if (ss->ssl3.hs.msg_len > 0)
continue; continue;
@ -11858,22 +11860,15 @@ ssl3_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
* data available for this message. If it can be done right out * data available for this message. If it can be done right out
* of the original buffer, then use it from there. * 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 */ /* handle it from input buffer */
rv = ssl3_HandleHandshakeMessage(ss, buf->buf, ss->ssl3.hs.msg_len, rv = ssl3_HandleHandshakeMessage(ss, buf.buf, ss->ssl3.hs.msg_len,
buf->len == ss->ssl3.hs.msg_len); buf.len == ss->ssl3.hs.msg_len);
if (rv == SECFailure) { buf.buf += ss->ssl3.hs.msg_len;
/* This test wants to fall through on either buf.len -= ss->ssl3.hs.msg_len;
* 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;
ss->ssl3.hs.msg_len = 0; ss->ssl3.hs.msg_len = 0;
ss->ssl3.hs.header_bytes = 0; ss->ssl3.hs.header_bytes = 0;
if (rv != SECSuccess) { /* return if SECWouldBlock. */ if (rv != SECSuccess) {
return rv; return rv;
} }
} else { } else {
@ -11881,7 +11876,7 @@ ssl3_HandleHandshake(sslSocket *ss, sslBuffer *origBuf)
unsigned int bytes; unsigned int bytes;
PORT_Assert(ss->ssl3.hs.msg_body.len < ss->ssl3.hs.msg_len); 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 */ /* Grow the buffer if needed */
rv = sslBuffer_Grow(&ss->ssl3.hs.msg_body, ss->ssl3.hs.msg_len); 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, 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; ss->ssl3.hs.msg_body.len += bytes;
buf->buf += bytes; buf.buf += bytes;
buf->len -= bytes; buf.len -= bytes;
PORT_Assert(ss->ssl3.hs.msg_body.len <= ss->ssl3.hs.msg_len); 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) { if (ss->ssl3.hs.msg_body.len == ss->ssl3.hs.msg_len) {
rv = ssl3_HandleHandshakeMessage( rv = ssl3_HandleHandshakeMessage(
ss, ss->ssl3.hs.msg_body.buf, ss->ssl3.hs.msg_len, ss, ss->ssl3.hs.msg_body.buf, ss->ssl3.hs.msg_len,
buf->len == 0); 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;
}
ss->ssl3.hs.msg_body.len = 0; ss->ssl3.hs.msg_body.len = 0;
ss->ssl3.hs.msg_len = 0; ss->ssl3.hs.msg_len = 0;
ss->ssl3.hs.header_bytes = 0; ss->ssl3.hs.header_bytes = 0;
if (rv != SECSuccess) { /* return if SECWouldBlock. */ if (rv != SECSuccess) {
return rv; return rv;
} }
} else { } else {
PORT_Assert(buf->len == 0); PORT_Assert(buf.len == 0);
break; break;
} }
} }
} /* end loop */ } /* end loop */
origBuf->len = 0; /* So ssl3_GatherAppDataRecord will keep looping. */ origBuf->len = 0; /* So ssl3_GatherAppDataRecord will keep looping. */
buf->buf = NULL; /* not a leak. */
return SECSuccess; return SECSuccess;
} }
@ -12183,7 +12170,7 @@ ssl3_UnprotectRecord(sslSocket *ss,
unsigned int hashBytes = MAX_MAC_LENGTH + 1; unsigned int hashBytes = MAX_MAC_LENGTH + 1;
SECStatus rv; SECStatus rv;
PORT_Assert(spec->direction == CipherSpecRead); PORT_Assert(spec->direction == ssl_secret_read);
good = ~0U; good = ~0U;
minLength = spec->macDef->mac_size; minLength = spec->macDef->mac_size;
@ -12372,7 +12359,7 @@ ssl3_HandleNonApplicationData(sslSocket *ss, SSLContentType rType,
ssl_GetSSL3HandshakeLock(ss); ssl_GetSSL3HandshakeLock(ss);
/* All the functions called in this switch MUST set error code if /* All the functions called in this switch MUST set error code if
** they return SECFailure or SECWouldBlock. ** they return SECFailure.
*/ */
switch (rType) { switch (rType) {
case ssl_ct_change_cipher_spec: 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) { if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3) {
/* Try to find the cipher spec. */ /* Try to find the cipher spec. */
newSpec = ssl_FindCipherSpecByEpoch(ss, CipherSpecRead, newSpec = ssl_FindCipherSpecByEpoch(ss, ssl_secret_read,
epoch); epoch);
if (newSpec != NULL) { if (newSpec != NULL) {
return newSpec; return newSpec;
@ -12694,8 +12681,8 @@ ssl3_InitState(sslSocket *ss)
ssl_GetSpecWriteLock(ss); ssl_GetSpecWriteLock(ss);
PR_INIT_CLIST(&ss->ssl3.hs.cipherSpecs); PR_INIT_CLIST(&ss->ssl3.hs.cipherSpecs);
rv = ssl_SetupNullCipherSpec(ss, CipherSpecRead); rv = ssl_SetupNullCipherSpec(ss, ssl_secret_read);
rv |= ssl_SetupNullCipherSpec(ss, CipherSpecWrite); rv |= ssl_SetupNullCipherSpec(ss, ssl_secret_write);
ss->ssl3.pwSpec = ss->ssl3.prSpec = NULL; ss->ssl3.pwSpec = ss->ssl3.prSpec = NULL;
ssl_ReleaseSpecWriteLock(ss); ssl_ReleaseSpecWriteLock(ss);
if (rv != SECSuccess) { if (rv != SECSuccess) {

View file

@ -51,6 +51,7 @@ static const ssl3ExtensionHandler clientHelloHandlers[] = {
{ ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ServerHandlePskModesXtn }, { ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ServerHandlePskModesXtn },
{ ssl_tls13_cookie_xtn, &tls13_ServerHandleCookieXtn }, { ssl_tls13_cookie_xtn, &tls13_ServerHandleCookieXtn },
{ ssl_tls13_encrypted_sni_xtn, &tls13_ServerHandleEsniXtn }, { ssl_tls13_encrypted_sni_xtn, &tls13_ServerHandleEsniXtn },
{ ssl_tls13_post_handshake_auth_xtn, &tls13_ServerHandlePostHandshakeAuthXtn },
{ ssl_record_size_limit_xtn, &ssl_HandleRecordSizeLimitXtn }, { ssl_record_size_limit_xtn, &ssl_HandleRecordSizeLimitXtn },
{ 0, NULL } { 0, NULL }
}; };
@ -138,6 +139,7 @@ static const sslExtensionBuilder clientHelloSendersTLS[] =
{ ssl_tls13_cookie_xtn, &tls13_ClientSendHrrCookieXtn }, { ssl_tls13_cookie_xtn, &tls13_ClientSendHrrCookieXtn },
{ ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ClientSendPskModesXtn }, { ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ClientSendPskModesXtn },
{ ssl_tls13_encrypted_sni_xtn, &tls13_ClientSendEsniXtn }, { ssl_tls13_encrypted_sni_xtn, &tls13_ClientSendEsniXtn },
{ ssl_tls13_post_handshake_auth_xtn, &tls13_ClientSendPostHandshakeAuthXtn },
{ ssl_record_size_limit_xtn, &ssl_SendRecordSizeLimitXtn }, { ssl_record_size_limit_xtn, &ssl_SendRecordSizeLimitXtn },
/* The pre_shared_key extension MUST be last. */ /* The pre_shared_key extension MUST be last. */
{ ssl_tls13_pre_shared_key_xtn, &tls13_ClientSendPreSharedKeyXtn }, { ssl_tls13_pre_shared_key_xtn, &tls13_ClientSendPreSharedKeyXtn },

View file

@ -389,7 +389,6 @@ dtls_GatherData(sslSocket *ss, sslGather *gs, int flags)
* application data is available. * application data is available.
* Returns 0 if ssl3_GatherData hits EOF. * Returns 0 if ssl3_GatherData hits EOF.
* Returns -1 on read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error. * 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, * Called from ssl_GatherRecord1stHandshake in sslcon.c,
* and from SSL_ForceHandshake in sslsecur.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)); PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
do { do {
PRBool handleRecordNow = PR_FALSE;
PRBool processingEarlyData; PRBool processingEarlyData;
ssl_GetSSL3HandshakeLock(ss); ssl_GetSSL3HandshakeLock(ss);
processingEarlyData = ss->ssl3.hs.zeroRttState == ssl_0rtt_accepted; 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 /* Without this, we may end up wrongly reporting
* SSL_ERROR_RX_UNEXPECTED_* errors if we receive any records from the * SSL_ERROR_RX_UNEXPECTED_* errors if we receive any records from the
* peer while we are waiting to be restarted. * peer while we are waiting to be restarted.
@ -439,93 +444,68 @@ ssl3_GatherCompleteHandshake(sslSocket *ss, int flags)
return (int)SECFailure; return (int)SECFailure;
} }
/* Treat an empty msgState like a NULL msgState. (Most of the time ssl_ReleaseSSL3HandshakeLock(ss);
* when ssl3_HandleHandshake returns SECWouldBlock, it leaves
* behind a non-NULL but zero-length msgState). /* State for SSLv2 client hello support. */
* Test: async_cert_restart_server_sends_hello_request_first_in_separate_record ssl2Gather ssl2gs = { PR_FALSE, 0 };
*/ ssl2Gather *ssl2gs_ptr = NULL;
if (ss->ssl3.hs.msgState.buf) {
if (ss->ssl3.hs.msgState.len == 0) { /* If we're a server and waiting for a client hello, accept v2. */
ss->ssl3.hs.msgState.buf = NULL; if (ss->sec.isServer && ss->opt.enableV2CompatibleHello &&
} else { ss->ssl3.hs.ws == wait_client_hello) {
handleRecordNow = PR_TRUE; 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) { if (ssl2gs.isV2) {
/* ssl3_HandleHandshake previously returned SECWouldBlock and the rv = ssl3_HandleV2ClientHello(ss, ss->gs.inbuf.buf,
* as-yet-unprocessed plaintext of that previous handshake record. ss->gs.inbuf.len,
* We need to process it now before we overwrite it with the next ssl2gs.padding);
* handshake record. if (rv < 0) {
*/
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) {
return rv; return rv;
} }
} else {
if (ssl2gs.isV2) { /* decipher it, and handle it if it's a handshake.
rv = ssl3_HandleV2ClientHello(ss, ss->gs.inbuf.buf, * If it's application data, ss->gs.buf will not be empty upon return.
ss->gs.inbuf.len, * If it's a change cipher spec, alert, or handshake message,
ssl2gs.padding); * ss->gs.buf.len will be 0 when ssl3_HandleRecord returns SECSuccess.
if (rv < 0) { *
return rv; * cText only needs to be valid for this next function call, so
} * it can borrow gs.hdr.
} else { */
/* decipher it, and handle it if it's a handshake. cText.hdr = ss->gs.hdr;
* If it's application data, ss->gs.buf will not be empty upon return. cText.hdrLen = ss->gs.hdrLen;
* If it's a change cipher spec, alert, or handshake message, cText.buf = &ss->gs.inbuf;
* ss->gs.buf.len will be 0 when ssl3_HandleRecord returns SECSuccess. rv = ssl3_HandleRecord(ss, &cText);
*
* 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) { if (rv < 0) {
return ss->recvdCloseNotify ? 0 : rv; return ss->recvdCloseNotify ? 0 : rv;
@ -575,7 +555,7 @@ ssl3_GatherCompleteHandshake(sslSocket *ss, int flags)
* delivered to the application before the handshake completes. */ * delivered to the application before the handshake completes. */
ssl_ReleaseSSL3HandshakeLock(ss); ssl_ReleaseSSL3HandshakeLock(ss);
PORT_SetError(PR_WOULD_BLOCK_ERROR); PORT_SetError(PR_WOULD_BLOCK_ERROR);
return SECWouldBlock; return -1;
} }
ssl_ReleaseSSL3HandshakeLock(ss); ssl_ReleaseSSL3HandshakeLock(ss);
} while (keepGoing); } while (keepGoing);
@ -596,7 +576,6 @@ ssl3_GatherCompleteHandshake(sslSocket *ss, int flags)
* Returns 1 when application data is available. * Returns 1 when application data is available.
* Returns 0 if ssl3_GatherData hits EOF. * Returns 0 if ssl3_GatherData hits EOF.
* Returns -1 on read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error. * 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 * Called from DoRecv in sslsecur.c
* Caller must hold the recv buf lock. * Caller must hold the recv buf lock.
@ -616,3 +595,108 @@ ssl3_GatherAppDataRecord(sslSocket *ss, int flags)
return rv; 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;
}

View file

@ -44,17 +44,13 @@ const char *ssl_version = "SECURITY_VERSION:"
* This function acquires and releases the RecvBufLock. * This function acquires and releases the RecvBufLock.
* *
* returns SECSuccess for success. * returns SECSuccess for success.
* returns SECWouldBlock when that value is returned by * returns SECFailure on error, setting PR_WOULD_BLOCK_ERROR if only blocked.
* ssl3_GatherCompleteHandshake().
* returns SECFailure on all other errors.
* *
* The gather functions called by ssl_GatherRecord1stHandshake are expected * The gather functions called by ssl_GatherRecord1stHandshake are expected
* to return values interpreted as follows: * to return values interpreted as follows:
* 1 : the function completed without error. * 1 : the function completed without error.
* 0 : the function read EOF. * 0 : the function read EOF.
* -1 : read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error. * -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 * This code is similar to, and easily confused with, DoRecv() in sslsecur.c
* *
@ -82,16 +78,14 @@ ssl_GatherRecord1stHandshake(sslSocket *ss)
ssl_ReleaseRecvBufLock(ss); ssl_ReleaseRecvBufLock(ss);
if (rv <= 0) { 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) { if (rv == 0) {
/* EOF. Loser */ /* EOF. Loser */
PORT_SetError(PR_END_OF_FILE_ERROR); 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. */ return SECFailure; /* rv is < 0 here. */
} }

View file

@ -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 blocking sockets, always returns len or SECFailure, no short writes.
* For non-blocking sockets: * For non-blocking sockets:
* Returns positive count if any data was written, else returns SECFailure. * Returns positive count if any data was written, else returns SECFailure.
* Short writes may occur. Does not return SECWouldBlock. * Short writes may occur.
*/ */
int int
ssl_DefSend(sslSocket *ss, const unsigned char *buf, int len, int flags) ssl_DefSend(sslSocket *ss, const unsigned char *buf, int len, int flags)

View file

@ -268,6 +268,7 @@ typedef enum {
SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION = (SSL_ERROR_BASE + 177), SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION = (SSL_ERROR_BASE + 177),
SSL_ERROR_MISSING_ESNI_EXTENSION = (SSL_ERROR_BASE + 178), SSL_ERROR_MISSING_ESNI_EXTENSION = (SSL_ERROR_BASE + 178),
SSL_ERROR_RX_UNEXPECTED_RECORD_TYPE = (SSL_ERROR_BASE + 179), 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. */ SSL_ERROR_END_OF_LIST /* let the c compiler determine the value of this. */
} SSLErrorCodes; } SSLErrorCodes;
#endif /* NO_SECURITY_ERROR_ENUM */ #endif /* NO_SECURITY_ERROR_ENUM */

View file

@ -350,6 +350,27 @@ typedef SSLHelloRetryRequestAction(PR_CALLBACK *SSLHelloRetryRequestCallback)(
(PRFileDesc * _fd, PRBool _requestUpdate), \ (PRFileDesc * _fd, PRBool _requestUpdate), \
(fd, 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. * Session cache API.
*/ */
@ -511,6 +532,100 @@ typedef SECStatus(PR_CALLBACK *SSLResumptionTokenCallback)(
group, pubKey, pad, notBefore, notAfter, \ group, pubKey, pad, notBefore, notAfter, \
out, outlen, maxlen)) 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 */ /* Deprecated experimental APIs */
#define SSL_UseAltServerHelloType(fd, enable) SSL_DEPRECATED_EXPERIMENTAL_API #define SSL_UseAltServerHelloType(fd, enable) SSL_DEPRECATED_EXPERIMENTAL_API

View file

@ -272,6 +272,7 @@ typedef struct sslOptionsStr {
unsigned int enableDtlsShortHeader : 1; unsigned int enableDtlsShortHeader : 1;
unsigned int enableHelloDowngradeCheck : 1; unsigned int enableHelloDowngradeCheck : 1;
unsigned int enableV2CompatibleHello : 1; unsigned int enableV2CompatibleHello : 1;
unsigned int enablePostHandshakeAuth : 1;
} sslOptions; } sslOptions;
typedef enum { sslHandshakingUndetermined = 0, typedef enum { sslHandshakingUndetermined = 0,
@ -622,8 +623,6 @@ typedef struct SSL3HandshakeStateStr {
unsigned long msg_len; unsigned long msg_len;
PRBool isResuming; /* we are resuming (not used in TLS 1.3) */ PRBool isResuming; /* we are resuming (not used in TLS 1.3) */
PRBool sendingSCSV; /* instead of empty RI */ 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 /* The session ticket received in a NewSessionTicket message is temporarily
* stored in newSessionTicket until the handshake is finished; then it is * stored in newSessionTicket until the handshake is finished; then it is
@ -744,10 +743,10 @@ struct ssl3StateStr {
* update is initiated locally. */ * update is initiated locally. */
PRBool peerRequestedKeyUpdate; PRBool peerRequestedKeyUpdate;
/* Internal callback for when we do a cipher suite change. Used for /* This is true after the server requests client certificate;
* debugging in TLS 1.3. This can only be set by non-public functions. */ * false after the client certificate is received. Used by the
sslCipherSpecChangedFunc changedCipherSpecFunc; * server. */
void *changedCipherSpecArg; PRBool clientCertRequested;
CERTCertificate *clientCertificate; /* used by client */ CERTCertificate *clientCertificate; /* used by client */
SECKEYPrivateKey *clientPrivateKey; /* used by client */ SECKEYPrivateKey *clientPrivateKey; /* used by client */
@ -994,6 +993,10 @@ struct sslSocketStr {
PRCList extensionHooks; PRCList extensionHooks;
SSLResumptionTokenCallback resumptionTokenCallback; SSLResumptionTokenCallback resumptionTokenCallback;
void *resumptionTokenContext; void *resumptionTokenContext;
SSLSecretCallback secretCallback;
void *secretCallbackArg;
SSLRecordWriteCallback recordWriteCallback;
void *recordWriteCallbackArg;
PRIntervalTime rTimeout; /* timeout for NSPR I/O */ PRIntervalTime rTimeout; /* timeout for NSPR I/O */
PRIntervalTime wTimeout; /* 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); const void *p, unsigned int l);
extern SECStatus ssl_BeginClientHandshake(sslSocket *ss); extern SECStatus ssl_BeginClientHandshake(sslSocket *ss);
extern SECStatus ssl_BeginServerHandshake(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, extern SECStatus ssl3_InitPendingCipherSpecs(sslSocket *ss, PK11SymKey *secret,
PRBool derive); PRBool derive);
@ -1742,6 +1745,17 @@ SECStatus SSLExp_GetResumptionTokenInfo(const PRUint8 *tokenData, unsigned int t
SECStatus SSLExp_DestroyResumptionTokenInfo(SSLResumptionTokenInfo *token); 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 #define SSLResumptionTokenVersion 2
SEC_END_PROTOS SEC_END_PROTOS

View file

@ -150,6 +150,7 @@ SSL_GetPreliminaryChannelInfo(PRFileDesc *fd,
} else { } else {
inf.maxEarlyDataSize = 0; inf.maxEarlyDataSize = 0;
} }
inf.zeroRttCipherSuite = ss->ssl3.hs.zeroRttSuite;
memcpy(info, &inf, inf.length); memcpy(info, &inf, inf.length);
return SECSuccess; return SECSuccess;
@ -234,89 +235,89 @@ SSL_GetPreliminaryChannelInfo(PRFileDesc *fd,
static const SSLCipherSuiteInfo suiteInfo[] = { static const SSLCipherSuiteInfo suiteInfo[] = {
/* <------ Cipher suite --------------------> <auth> <KEA> <bulk cipher> <MAC> <FIPS> */ /* <------ Cipher suite --------------------> <auth> <KEA> <bulk cipher> <MAC> <FIPS> */
{ 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_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 }, { 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 }, { 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(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 }, { 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_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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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_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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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_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 }, { 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 }, { 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_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 }, { 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 }, { 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_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 }, { 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 }, { 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 */ /* 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_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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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_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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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_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 }, { 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 }, { 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 }, { 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 }, { 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_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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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 }, { 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_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 }, { 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 }, { 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])) #define NUM_SUITEINFOS ((sizeof suiteInfo) / (sizeof suiteInfo[0]))

View file

@ -16,32 +16,7 @@
#include "nss.h" /* for NSS_RegisterShutdown */ #include "nss.h" /* for NSS_RegisterShutdown */
#include "prinit.h" /* for PR_CallOnceWithArg */ #include "prinit.h" /* for PR_CallOnceWithArg */
/* Returns a SECStatus: SECSuccess or SECFailure, NOT SECWouldBlock. /* Step through the handshake functions.
*
* 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()
* *
* Called from: SSL_ForceHandshake (below), * Called from: SSL_ForceHandshake (below),
* ssl_SecureRecv (below) and * ssl_SecureRecv (below) and
@ -52,10 +27,10 @@
* *
* Caller must hold the (write) handshakeLock. * Caller must hold the (write) handshakeLock.
*/ */
int SECStatus
ssl_Do1stHandshake(sslSocket *ss) ssl_Do1stHandshake(sslSocket *ss)
{ {
int rv = SECSuccess; SECStatus rv = SECSuccess;
while (ss->handshake && rv == SECSuccess) { while (ss->handshake && rv == SECSuccess) {
PORT_Assert(ss->opt.noLocks || ssl_Have1stHandshakeLock(ss)); 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_HaveXmitBufLock(ss));
PORT_Assert(ss->opt.noLocks || !ssl_HaveSSL3HandshakeLock(ss)); PORT_Assert(ss->opt.noLocks || !ssl_HaveSSL3HandshakeLock(ss));
if (rv == SECWouldBlock) {
PORT_SetError(PR_WOULD_BLOCK_ERROR);
rv = SECFailure;
}
return rv; return rv;
} }
@ -106,8 +77,8 @@ ssl_FinishHandshake(sslSocket *ss)
static SECStatus static SECStatus
ssl3_AlwaysBlock(sslSocket *ss) ssl3_AlwaysBlock(sslSocket *ss)
{ {
PORT_SetError(PR_WOULD_BLOCK_ERROR); /* perhaps redundant. */ PORT_SetError(PR_WOULD_BLOCK_ERROR);
return SECWouldBlock; return SECFailure;
} }
/* /*
@ -400,10 +371,13 @@ SSL_ForceHandshake(PRFileDesc *fd)
ssl_ReleaseRecvBufLock(ss); ssl_ReleaseRecvBufLock(ss);
if (gatherResult > 0) { if (gatherResult > 0) {
rv = SECSuccess; rv = SECSuccess;
} else if (gatherResult == 0) { } else {
PORT_SetError(PR_END_OF_FILE_ERROR); if (gatherResult == 0) {
} else if (gatherResult == SECWouldBlock) { PORT_SetError(PR_END_OF_FILE_ERROR);
PORT_SetError(PR_WOULD_BLOCK_ERROR); }
/* We can rely on ssl3_GatherCompleteHandshake to set
* PR_WOULD_BLOCK_ERROR as needed here. */
rv = SECFailure;
} }
} else { } else {
PORT_Assert(!ss->firstHsDone); PORT_Assert(!ss->firstHsDone);
@ -515,8 +489,7 @@ DoRecv(sslSocket *ss, unsigned char *out, int len, int flags)
SSL_GETPID(), ss->fd)); SSL_GETPID(), ss->fd));
goto done; goto done;
} }
if ((rv != SECWouldBlock) && if (PR_GetError() != PR_WOULD_BLOCK_ERROR) {
(PR_GetError() != PR_WOULD_BLOCK_ERROR)) {
/* Some random error */ /* Some random error */
goto done; goto done;
} }
@ -741,7 +714,7 @@ ssl_SecureShutdown(sslSocket *ss, int nsprHow)
/************************************************************************/ /************************************************************************/
static SECStatus static SECStatus
tls13_CheckKeyUpdate(sslSocket *ss, CipherSpecDirection dir) tls13_CheckKeyUpdate(sslSocket *ss, SSLSecretDirection dir)
{ {
PRBool keyUpdate; PRBool keyUpdate;
ssl3CipherSpec *spec; ssl3CipherSpec *spec;
@ -765,7 +738,7 @@ tls13_CheckKeyUpdate(sslSocket *ss, CipherSpecDirection dir)
* having the write margin larger reduces the number of times that a * having the write margin larger reduces the number of times that a
* KeyUpdate is sent by a reader. */ * KeyUpdate is sent by a reader. */
ssl_GetSpecReadLock(ss); ssl_GetSpecReadLock(ss);
if (dir == CipherSpecRead) { if (dir == ssl_secret_read) {
spec = ss->ssl3.crSpec; spec = ss->ssl3.crSpec;
margin = spec->cipherDef->max_records / 8; margin = spec->cipherDef->max_records / 8;
} else { } 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_TRC(5, ("%d: SSL[%d]: automatic key update at %llx for %s cipher spec",
SSL_GETPID(), ss->fd, seqNum, SSL_GETPID(), ss->fd, seqNum,
(dir == CipherSpecRead) ? "read" : "write")); (dir == ssl_secret_read) ? "read" : "write"));
ssl_GetSSL3HandshakeLock(ss); ssl_GetSSL3HandshakeLock(ss);
rv = tls13_SendKeyUpdate(ss, (dir == CipherSpecRead) ? update_requested : update_not_requested, rv = tls13_SendKeyUpdate(ss, (dir == ssl_secret_read) ? update_requested : update_not_requested,
dir == CipherSpecWrite /* buffer */); dir == ssl_secret_write /* buffer */);
ssl_ReleaseSSL3HandshakeLock(ss); ssl_ReleaseSSL3HandshakeLock(ss);
return rv; return rv;
} }
@ -829,7 +802,7 @@ ssl_SecureRecv(sslSocket *ss, unsigned char *buf, int len, int flags)
} }
ssl_Release1stHandshakeLock(ss); ssl_Release1stHandshakeLock(ss);
} else { } else {
if (tls13_CheckKeyUpdate(ss, CipherSpecRead) != SECSuccess) { if (tls13_CheckKeyUpdate(ss, ssl_secret_read) != SECSuccess) {
rv = PR_FAILURE; rv = PR_FAILURE;
} }
} }
@ -955,7 +928,7 @@ ssl_SecureSend(sslSocket *ss, const unsigned char *buf, int len, int flags)
} }
if (ss->firstHsDone) { if (ss->firstHsDone) {
if (tls13_CheckKeyUpdate(ss, CipherSpecWrite) != SECSuccess) { if (tls13_CheckKeyUpdate(ss, ssl_secret_write) != SECSuccess) {
rv = PR_FAILURE; rv = PR_FAILURE;
goto done; goto done;
} }
@ -1010,6 +983,35 @@ ssl_SecureWrite(sslSocket *ss, const unsigned char *buf, int len)
return ssl_SecureSend(ss, buf, len, 0); 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 SECStatus
SSL_AlertReceivedCallback(PRFileDesc *fd, SSLAlertCallback cb, void *arg) SSL_AlertReceivedCallback(PRFileDesc *fd, SSLAlertCallback cb, void *arg)
{ {

View file

@ -86,7 +86,8 @@ static sslOptions ssl_defaults = {
.enableTls13CompatMode = PR_FALSE, .enableTls13CompatMode = PR_FALSE,
.enableDtlsShortHeader = PR_FALSE, .enableDtlsShortHeader = PR_FALSE,
.enableHelloDowngradeCheck = 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; ss->opt.enableV2CompatibleHello = val;
break; break;
case SSL_ENABLE_POST_HANDSHAKE_AUTH:
ss->opt.enablePostHandshakeAuth = val;
break;
default: default:
PORT_SetError(SEC_ERROR_INVALID_ARGS); PORT_SetError(SEC_ERROR_INVALID_ARGS);
rv = SECFailure; rv = SECFailure;
@ -990,6 +995,9 @@ SSL_OptionGet(PRFileDesc *fd, PRInt32 which, PRIntn *pVal)
case SSL_ENABLE_V2_COMPATIBLE_HELLO: case SSL_ENABLE_V2_COMPATIBLE_HELLO:
val = ss->opt.enableV2CompatibleHello; val = ss->opt.enableV2CompatibleHello;
break; break;
case SSL_ENABLE_POST_HANDSHAKE_AUTH:
val = ss->opt.enablePostHandshakeAuth;
break;
default: default:
PORT_SetError(SEC_ERROR_INVALID_ARGS); PORT_SetError(SEC_ERROR_INVALID_ARGS);
rv = SECFailure; rv = SECFailure;
@ -1122,6 +1130,9 @@ SSL_OptionGetDefault(PRInt32 which, PRIntn *pVal)
case SSL_ENABLE_V2_COMPATIBLE_HELLO: case SSL_ENABLE_V2_COMPATIBLE_HELLO:
val = ssl_defaults.enableV2CompatibleHello; val = ssl_defaults.enableV2CompatibleHello;
break; break;
case SSL_ENABLE_POST_HANDSHAKE_AUTH:
val = ssl_defaults.enablePostHandshakeAuth;
break;
default: default:
PORT_SetError(SEC_ERROR_INVALID_ARGS); PORT_SetError(SEC_ERROR_INVALID_ARGS);
rv = SECFailure; rv = SECFailure;
@ -1325,6 +1336,10 @@ SSL_OptionSetDefault(PRInt32 which, PRIntn val)
ssl_defaults.enableV2CompatibleHello = val; ssl_defaults.enableV2CompatibleHello = val;
break; break;
case SSL_ENABLE_POST_HANDSHAKE_AUTH:
ssl_defaults.enablePostHandshakeAuth = val;
break;
default: default:
PORT_SetError(SEC_ERROR_INVALID_ARGS); PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure; return SECFailure;
@ -4026,20 +4041,25 @@ struct {
void *function; void *function;
} ssl_experimental_functions[] = { } ssl_experimental_functions[] = {
#ifndef SSL_DISABLE_EXPERIMENTAL_API #ifndef SSL_DISABLE_EXPERIMENTAL_API
EXP(DestroyResumptionTokenInfo),
EXP(EnableESNI),
EXP(EncodeESNIKeys),
EXP(GetCurrentEpoch),
EXP(GetExtensionSupport), EXP(GetExtensionSupport),
EXP(GetResumptionTokenInfo),
EXP(HelloRetryRequestCallback), EXP(HelloRetryRequestCallback),
EXP(InstallExtensionHooks), EXP(InstallExtensionHooks),
EXP(KeyUpdate), EXP(KeyUpdate),
EXP(RecordLayerData),
EXP(RecordLayerWriteCallback),
EXP(SecretCallback),
EXP(SendCertificateRequest),
EXP(SendSessionTicket), EXP(SendSessionTicket),
EXP(SetESNIKeyPair),
EXP(SetMaxEarlyDataSize), EXP(SetMaxEarlyDataSize),
EXP(SetupAntiReplay),
EXP(SetResumptionTokenCallback), EXP(SetResumptionTokenCallback),
EXP(SetResumptionToken), EXP(SetResumptionToken),
EXP(GetResumptionTokenInfo), EXP(SetupAntiReplay),
EXP(DestroyResumptionTokenInfo),
EXP(SetESNIKeyPair),
EXP(EncodeESNIKeys),
EXP(EnableESNI),
#endif #endif
{ "", NULL } { "", NULL }
}; };

View file

@ -113,7 +113,7 @@ ssl_GetMacDef(const sslSocket *ss, const ssl3CipherSuiteDef *suiteDef)
} }
ssl3CipherSpec * ssl3CipherSpec *
ssl_FindCipherSpecByEpoch(sslSocket *ss, CipherSpecDirection direction, ssl_FindCipherSpecByEpoch(sslSocket *ss, SSLSecretDirection direction,
DTLSEpoch epoch) DTLSEpoch epoch)
{ {
PRCList *cur_p; PRCList *cur_p;
@ -134,7 +134,7 @@ ssl_FindCipherSpecByEpoch(sslSocket *ss, CipherSpecDirection direction,
} }
ssl3CipherSpec * ssl3CipherSpec *
ssl_CreateCipherSpec(sslSocket *ss, CipherSpecDirection direction) ssl_CreateCipherSpec(sslSocket *ss, SSLSecretDirection direction)
{ {
ssl3CipherSpec *spec = PORT_ZNew(ssl3CipherSpec); ssl3CipherSpec *spec = PORT_ZNew(ssl3CipherSpec);
if (!spec) { if (!spec) {
@ -159,7 +159,7 @@ ssl_SaveCipherSpec(sslSocket *ss, ssl3CipherSpec *spec)
/* Called from ssl3_InitState. */ /* Called from ssl3_InitState. */
/* Caller must hold the SpecWriteLock. */ /* Caller must hold the SpecWriteLock. */
SECStatus SECStatus
ssl_SetupNullCipherSpec(sslSocket *ss, CipherSpecDirection dir) ssl_SetupNullCipherSpec(sslSocket *ss, SSLSecretDirection dir)
{ {
ssl3CipherSpec *spec; ssl3CipherSpec *spec;
@ -187,7 +187,7 @@ ssl_SetupNullCipherSpec(sslSocket *ss, CipherSpecDirection dir)
dtls_InitRecvdRecords(&spec->recvdRecords); dtls_InitRecvdRecords(&spec->recvdRecords);
ssl_SaveCipherSpec(ss, spec); ssl_SaveCipherSpec(ss, spec);
if (dir == CipherSpecRead) { if (dir == ssl_secret_read) {
ss->ssl3.crSpec = spec; ss->ssl3.crSpec = spec;
} else { } else {
ss->ssl3.cwSpec = spec; ss->ssl3.cwSpec = spec;
@ -259,13 +259,13 @@ ssl_DestroyCipherSpecs(PRCList *list)
} }
void void
ssl_CipherSpecReleaseByEpoch(sslSocket *ss, CipherSpecDirection dir, ssl_CipherSpecReleaseByEpoch(sslSocket *ss, SSLSecretDirection dir,
DTLSEpoch epoch) DTLSEpoch epoch)
{ {
ssl3CipherSpec *spec; ssl3CipherSpec *spec;
SSL_TRC(10, ("%d: SSL[%d]: releasing %s cipher spec for epoch %d", SSL_TRC(10, ("%d: SSL[%d]: releasing %s cipher spec for epoch %d",
SSL_GETPID(), ss->fd, SSL_GETPID(), ss->fd,
(dir == CipherSpecRead) ? "read" : "write", epoch)); (dir == ssl_secret_read) ? "read" : "write", epoch));
spec = ssl_FindCipherSpecByEpoch(ss, dir, epoch); spec = ssl_FindCipherSpecByEpoch(ss, dir, epoch);
if (spec) { if (spec) {

View file

@ -19,13 +19,8 @@ typedef enum {
TrafficKeyApplicationData = 3 TrafficKeyApplicationData = 3
} TrafficKeyType; } TrafficKeyType;
typedef enum {
CipherSpecRead,
CipherSpecWrite,
} CipherSpecDirection;
#define SPEC_DIR(spec) \ #define SPEC_DIR(spec) \
((spec->direction == CipherSpecRead) ? "read" : "write") ((spec->direction == ssl_secret_read) ? "read" : "write")
typedef struct ssl3CipherSpecStr ssl3CipherSpec; typedef struct ssl3CipherSpecStr ssl3CipherSpec;
typedef struct ssl3BulkCipherDefStr ssl3BulkCipherDef; typedef struct ssl3BulkCipherDefStr ssl3BulkCipherDef;
@ -146,7 +141,7 @@ struct ssl3CipherSpecStr {
PRCList link; PRCList link;
PRUint8 refCt; PRUint8 refCt;
CipherSpecDirection direction; SSLSecretDirection direction;
SSL3ProtocolVersion version; SSL3ProtocolVersion version;
SSL3ProtocolVersion recordVersion; SSL3ProtocolVersion recordVersion;
@ -184,17 +179,17 @@ const ssl3BulkCipherDef *ssl_GetBulkCipherDef(const ssl3CipherSuiteDef *cipher_d
const ssl3MACDef *ssl_GetMacDefByAlg(SSL3MACAlgorithm mac); const ssl3MACDef *ssl_GetMacDefByAlg(SSL3MACAlgorithm mac);
const ssl3MACDef *ssl_GetMacDef(const sslSocket *ss, const ssl3CipherSuiteDef *suiteDef); 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_SaveCipherSpec(sslSocket *ss, ssl3CipherSpec *spec);
void ssl_CipherSpecAddRef(ssl3CipherSpec *spec); void ssl_CipherSpecAddRef(ssl3CipherSpec *spec);
void ssl_CipherSpecRelease(ssl3CipherSpec *spec); void ssl_CipherSpecRelease(ssl3CipherSpec *spec);
void ssl_DestroyCipherSpecs(PRCList *list); void ssl_DestroyCipherSpecs(PRCList *list);
SECStatus ssl_SetupNullCipherSpec(sslSocket *ss, CipherSpecDirection dir); SECStatus ssl_SetupNullCipherSpec(sslSocket *ss, SSLSecretDirection dir);
ssl3CipherSpec *ssl_FindCipherSpecByEpoch(sslSocket *ss, ssl3CipherSpec *ssl_FindCipherSpecByEpoch(sslSocket *ss,
CipherSpecDirection direction, SSLSecretDirection direction,
DTLSEpoch epoch); DTLSEpoch epoch);
void ssl_CipherSpecReleaseByEpoch(sslSocket *ss, CipherSpecDirection direction, void ssl_CipherSpecReleaseByEpoch(sslSocket *ss, SSLSecretDirection direction,
DTLSEpoch epoch); DTLSEpoch epoch);
#endif /* __sslspec_h_ */ #endif /* __sslspec_h_ */

View file

@ -43,6 +43,11 @@ typedef enum {
ssl_ct_ack = 25 ssl_ct_ack = 25
} SSLContentType; } SSLContentType;
typedef enum {
ssl_secret_read = 1,
ssl_secret_write = 2,
} SSLSecretDirection;
typedef struct SSL3StatisticsStr { typedef struct SSL3StatisticsStr {
/* statistics from ssl3_SendClientHello (sch) */ /* statistics from ssl3_SendClientHello (sch) */
long sch_sid_cache_hits; long sch_sid_cache_hits;
@ -328,6 +333,9 @@ typedef struct SSLChannelInfoStr {
/* Preliminary channel info */ /* Preliminary channel info */
#define ssl_preinfo_version (1U << 0) #define ssl_preinfo_version (1U << 0)
#define ssl_preinfo_cipher_suite (1U << 1) #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) #define ssl_preinfo_all (ssl_preinfo_version | ssl_preinfo_cipher_suite)
typedef struct SSLPreliminaryChannelInfoStr { typedef struct SSLPreliminaryChannelInfoStr {
@ -359,6 +367,13 @@ typedef struct SSLPreliminaryChannelInfoStr {
* resume this session. */ * resume this session. */
PRUint32 maxEarlyDataSize; 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 /* When adding new fields to this structure, please document the
* NSS version in which they were added. */ * NSS version in which they were added. */
} SSLPreliminaryChannelInfo; } SSLPreliminaryChannelInfo;
@ -407,6 +422,12 @@ typedef struct SSLCipherSuiteInfoStr {
* this instead of |authAlgorithm|. */ * this instead of |authAlgorithm|. */
SSLAuthType authType; 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 /* When adding new fields to this structure, please document the
* NSS version in which they were added. */ * NSS version in which they were added. */
} SSLCipherSuiteInfo; } SSLCipherSuiteInfo;
@ -450,6 +471,7 @@ typedef enum {
ssl_tls13_psk_key_exchange_modes_xtn = 45, ssl_tls13_psk_key_exchange_modes_xtn = 45,
ssl_tls13_ticket_early_data_info_xtn = 46, /* Deprecated. */ ssl_tls13_ticket_early_data_info_xtn = 46, /* Deprecated. */
ssl_tls13_certificate_authorities_xtn = 47, ssl_tls13_certificate_authorities_xtn = 47,
ssl_tls13_post_handshake_auth_xtn = 49,
ssl_signature_algorithms_cert_xtn = 50, ssl_signature_algorithms_cert_xtn = 50,
ssl_tls13_key_share_xtn = 51, ssl_tls13_key_share_xtn = 51,
ssl_next_proto_nego_xtn = 13172, /* Deprecated. */ ssl_next_proto_nego_xtn = 13172, /* Deprecated. */

View file

@ -26,7 +26,7 @@
#include "tls13hashstate.h" #include "tls13hashstate.h"
static SECStatus tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch, static SECStatus tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch,
CipherSpecDirection install, SSLSecretDirection install,
PRBool deleteSecret); PRBool deleteSecret);
static SECStatus tls13_AESGCM( static SECStatus tls13_AESGCM(
ssl3KeyMaterial *keys, ssl3KeyMaterial *keys,
@ -56,6 +56,7 @@ static SECStatus tls13_SendCertificate(sslSocket *ss);
static SECStatus tls13_HandleCertificate( static SECStatus tls13_HandleCertificate(
sslSocket *ss, PRUint8 *b, PRUint32 length); sslSocket *ss, PRUint8 *b, PRUint32 length);
static SECStatus tls13_ReinjectHandshakeTranscript(sslSocket *ss); static SECStatus tls13_ReinjectHandshakeTranscript(sslSocket *ss);
static SECStatus tls13_SendCertificateRequest(sslSocket *ss);
static SECStatus tls13_HandleCertificateRequest(sslSocket *ss, PRUint8 *b, static SECStatus tls13_HandleCertificateRequest(sslSocket *ss, PRUint8 *b,
PRUint32 length); PRUint32 length);
static SECStatus static SECStatus
@ -104,6 +105,9 @@ static SECStatus tls13_ComputeFinished(
PRBool sending, PRUint8 *output, unsigned int *outputLen, PRBool sending, PRUint8 *output, unsigned int *outputLen,
unsigned int maxOutputLen); unsigned int maxOutputLen);
static SECStatus tls13_SendClientSecondRound(sslSocket *ss); static SECStatus tls13_SendClientSecondRound(sslSocket *ss);
static SECStatus tls13_SendClientSecondFlight(sslSocket *ss,
PRBool sendClientCert,
SSL3AlertDescription *sendAlert);
static SECStatus tls13_FinishHandshake(sslSocket *ss); static SECStatus tls13_FinishHandshake(sslSocket *ss);
const char kHkdfLabelClient[] = "c"; const char kHkdfLabelClient[] = "c";
@ -588,13 +592,13 @@ loser:
} }
static PRBool 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 ** static PK11SymKey **
tls13_TrafficSecretRef(sslSocket *ss, CipherSpecDirection direction) tls13_TrafficSecretRef(sslSocket *ss, SSLSecretDirection direction)
{ {
if (tls13_UseServerSecret(ss, direction)) { if (tls13_UseServerSecret(ss, direction)) {
return &ss->ssl3.hs.serverTrafficSecret; return &ss->ssl3.hs.serverTrafficSecret;
@ -603,7 +607,7 @@ tls13_TrafficSecretRef(sslSocket *ss, CipherSpecDirection direction)
} }
SECStatus SECStatus
tls13_UpdateTrafficKeys(sslSocket *ss, CipherSpecDirection direction) tls13_UpdateTrafficKeys(sslSocket *ss, SSLSecretDirection direction)
{ {
PK11SymKey **secret; PK11SymKey **secret;
PK11SymKey *updatedSecret; PK11SymKey *updatedSecret;
@ -626,7 +630,7 @@ tls13_UpdateTrafficKeys(sslSocket *ss, CipherSpecDirection direction)
*secret = updatedSecret; *secret = updatedSecret;
ssl_GetSpecReadLock(ss); ssl_GetSpecReadLock(ss);
if (direction == CipherSpecRead) { if (direction == ssl_secret_read) {
epoch = ss->ssl3.crSpec->epoch; epoch = ss->ssl3.crSpec->epoch;
} else { } else {
epoch = ss->ssl3.cwSpec->epoch; epoch = ss->ssl3.cwSpec->epoch;
@ -640,6 +644,11 @@ tls13_UpdateTrafficKeys(sslSocket *ss, CipherSpecDirection direction)
} }
++epoch; ++epoch;
if (ss->secretCallback) {
ss->secretCallback(ss->fd, epoch, direction, updatedSecret,
ss->secretCallbackArg);
}
rv = tls13_SetCipherSpec(ss, epoch, direction, PR_FALSE); rv = tls13_SetCipherSpec(ss, epoch, direction, PR_FALSE);
if (rv != SECSuccess) { if (rv != SECSuccess) {
FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
@ -698,7 +707,7 @@ tls13_SendKeyUpdate(sslSocket *ss, tls13KeyUpdateRequest request, PRBool buffer)
} }
ssl_ReleaseXmitBufLock(ss); ssl_ReleaseXmitBufLock(ss);
rv = tls13_UpdateTrafficKeys(ss, CipherSpecWrite); rv = tls13_UpdateTrafficKeys(ss, ssl_secret_write);
if (rv != SECSuccess) { if (rv != SECSuccess) {
goto loser; /* error code set by tls13_UpdateTrafficKeys */ goto loser; /* error code set by tls13_UpdateTrafficKeys */
} }
@ -791,7 +800,7 @@ tls13_HandleKeyUpdate(sslSocket *ss, PRUint8 *b, unsigned int length)
return SECFailure; return SECFailure;
} }
rv = tls13_UpdateTrafficKeys(ss, CipherSpecRead); rv = tls13_UpdateTrafficKeys(ss, ssl_secret_read);
if (rv != SECSuccess) { if (rv != SECSuccess) {
return SECFailure; /* Error code set by tls13_UpdateTrafficKeys. */ return SECFailure; /* Error code set by tls13_UpdateTrafficKeys. */
} }
@ -820,6 +829,56 @@ tls13_HandleKeyUpdate(sslSocket *ss, PRUint8 *b, unsigned int length)
return SECSuccess; 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 SECStatus
tls13_HandlePostHelloHandshakeMessage(sslSocket *ss, PRUint8 *b, PRUint32 length) tls13_HandlePostHelloHandshakeMessage(sslSocket *ss, PRUint8 *b, PRUint32 length)
{ {
@ -1030,6 +1089,13 @@ tls13_DeriveEarlySecrets(sslSocket *ss)
return SECFailure; 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, rv = tls13_DeriveSecretWrap(ss, ss->ssl3.hs.currentSecret,
NULL, kHkdfLabelEarlyExporterSecret, NULL, kHkdfLabelEarlyExporterSecret,
keylogLabelEarlyExporterSecret, keylogLabelEarlyExporterSecret,
@ -1098,6 +1164,18 @@ tls13_ComputeHandshakeSecrets(sslSocket *ss)
return rv; 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_TRC(5, ("%d: TLS13[%d]: compute master secret (%s)",
SSL_GETPID(), ss->fd, SSL_ROLE(ss))); SSL_GETPID(), ss->fd, SSL_ROLE(ss)));
@ -1148,6 +1226,18 @@ tls13_ComputeApplicationSecrets(sslSocket *ss)
return SECFailure; 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, rv = tls13_DeriveSecretWrap(ss, ss->ssl3.hs.currentSecret,
NULL, kHkdfLabelExporterMasterSecret, NULL, kHkdfLabelExporterMasterSecret,
keylogLabelExporterSecret, keylogLabelExporterSecret,
@ -1294,6 +1384,8 @@ tls13_NegotiateZeroRtt(sslSocket *ss, const sslSessionID *sid)
PORT_Assert(ss->statelessResume); PORT_Assert(ss->statelessResume);
ss->ssl3.hs.zeroRttState = ssl_0rtt_accepted; ss->ssl3.hs.zeroRttState = ssl_0rtt_accepted;
ss->ssl3.hs.zeroRttIgnore = ssl_0rtt_ignore_none; 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. */ /* Check if the offered group is acceptable. */
@ -2101,8 +2193,27 @@ tls13_SendCertificateRequest(sslSocket *ss)
/* We should always have at least one of these. */ /* We should always have at least one of these. */
PORT_Assert(SSL_BUFFER_LEN(&extensionBuf) > 0); 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, rv = ssl3_AppendHandshakeHeader(ss, ssl_hs_certificate_request,
1 + 0 + /* empty request context */ 1 + /* request context length */
ss->xtnData.certReqContext.len +
2 + /* extension length */ 2 + /* extension length */
SSL_BUFFER_LEN(&extensionBuf)); SSL_BUFFER_LEN(&extensionBuf));
if (rv != SECSuccess) { if (rv != SECSuccess) {
@ -2110,7 +2221,8 @@ tls13_SendCertificateRequest(sslSocket *ss)
} }
/* Context. */ /* Context. */
rv = ssl3_AppendHandshakeNumber(ss, 0, 1); rv = ssl3_AppendHandshakeVariable(ss, ss->xtnData.certReqContext.data,
ss->xtnData.certReqContext.len, 1);
if (rv != SECSuccess) { if (rv != SECSuccess) {
goto loser; /* err set by AppendHandshake. */ goto loser; /* err set by AppendHandshake. */
} }
@ -2198,7 +2310,7 @@ tls13_HandleHelloRetryRequest(sslSocket *ss, const PRUint8 *savedMsg,
/* Restore the null cipher spec for writing. */ /* Restore the null cipher spec for writing. */
ssl_GetSpecWriteLock(ss); ssl_GetSpecWriteLock(ss);
ssl_CipherSpecRelease(ss->ssl3.cwSpec); ssl_CipherSpecRelease(ss->ssl3.cwSpec);
ss->ssl3.cwSpec = ssl_FindCipherSpecByEpoch(ss, CipherSpecWrite, ss->ssl3.cwSpec = ssl_FindCipherSpecByEpoch(ss, ssl_secret_write,
TrafficKeyClearText); TrafficKeyClearText);
PORT_Assert(ss->ssl3.cwSpec); PORT_Assert(ss->ssl3.cwSpec);
ssl_ReleaseSpecWriteLock(ss); ssl_ReleaseSpecWriteLock(ss);
@ -2274,25 +2386,49 @@ tls13_HandleCertificateRequest(sslSocket *ss, PRUint8 *b, PRUint32 length)
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
/* Client */ /* Client */
rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST, if (ss->opt.enablePostHandshakeAuth) {
wait_cert_request); 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) { if (rv != SECSuccess) {
return SECFailure; return SECFailure;
} }
PORT_Assert(ss->ssl3.clientCertChain == NULL); if (ss->firstHsDone) {
PORT_Assert(ss->ssl3.clientCertificate == NULL); /* clean up anything left from previous handshake. */
PORT_Assert(ss->ssl3.clientPrivateKey == NULL); if (ss->ssl3.clientCertChain != NULL) {
PORT_Assert(!ss->ssl3.hs.clientCertRequested); 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); rv = ssl3_ConsumeHandshakeVariable(ss, &context, 1, &b, &length);
if (rv != SECSuccess) { if (rv != SECSuccess) {
return SECFailure; return SECFailure;
} }
/* We don't support post-handshake client auth, the certificate request /* Unless it is a post-handshake client auth, the certificate
* context must always be empty. */ * request context must be empty. */
if (context.len > 0) { if (!ss->firstHsDone && context.len > 0) {
FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CERT_REQUEST, illegal_parameter); FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CERT_REQUEST, illegal_parameter);
return SECFailure; return SECFailure;
} }
@ -2326,7 +2462,35 @@ tls13_HandleCertificateRequest(sslSocket *ss, PRUint8 *b, PRUint32 length)
} }
ss->ssl3.hs.clientCertRequested = PR_TRUE; 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; return SECSuccess;
} }
@ -2348,7 +2512,7 @@ tls13_SendEncryptedServerSequence(sslSocket *ss)
} }
rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake, rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake,
CipherSpecWrite, PR_FALSE); ssl_secret_write, PR_FALSE);
if (rv != SECSuccess) { if (rv != SECSuccess) {
LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE); LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE);
return SECFailure; return SECFailure;
@ -2458,7 +2622,7 @@ tls13_SendServerHelloSequence(sslSocket *ss)
} }
rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData, rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData,
CipherSpecWrite, PR_FALSE); ssl_secret_write, PR_FALSE);
if (rv != SECSuccess) { if (rv != SECSuccess) {
LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE); LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE);
return SECFailure; return SECFailure;
@ -2470,7 +2634,7 @@ tls13_SendServerHelloSequence(sslSocket *ss)
} }
if (ss->ssl3.hs.zeroRttState == ssl_0rtt_accepted) { if (ss->ssl3.hs.zeroRttState == ssl_0rtt_accepted) {
rv = tls13_SetCipherSpec(ss, TrafficKeyEarlyApplicationData, rv = tls13_SetCipherSpec(ss, TrafficKeyEarlyApplicationData,
CipherSpecRead, PR_TRUE); ssl_secret_read, PR_TRUE);
if (rv != SECSuccess) { if (rv != SECSuccess) {
LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE); LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE);
return SECFailure; return SECFailure;
@ -2482,7 +2646,7 @@ tls13_SendServerHelloSequence(sslSocket *ss)
rv = tls13_SetCipherSpec(ss, rv = tls13_SetCipherSpec(ss,
TrafficKeyHandshake, TrafficKeyHandshake,
CipherSpecRead, PR_FALSE); ssl_secret_read, PR_FALSE);
if (rv != SECSuccess) { if (rv != SECSuccess) {
LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE); LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE);
return SECFailure; 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 /* 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 * send another ClientHello in response to a HelloRetryRequest. Now
* that we won't be receiving a HelloRetryRequest, release the spec. */ * 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, rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake,
CipherSpecRead, PR_FALSE); ssl_secret_read, PR_FALSE);
if (rv != SECSuccess) { if (rv != SECSuccess) {
FATAL_ERROR(ss, SSL_ERROR_INIT_CIPHER_SUITE_FAILURE, internal_error); FATAL_ERROR(ss, SSL_ERROR_INIT_CIPHER_SUITE_FAILURE, internal_error);
return SECFailure; return SECFailure;
@ -2862,8 +3026,13 @@ tls13_HandleCertificate(sslSocket *ss, PRUint8 *b, PRUint32 length)
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
if (ss->sec.isServer) { if (ss->sec.isServer) {
rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE, if (ss->ssl3.clientCertRequested) {
wait_client_cert); 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 { } else {
rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE, rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE,
wait_cert_request, wait_server_cert); 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. */ /* We can ignore any other cleartext from the client. */
if (ss->sec.isServer && IS_DTLS(ss)) { if (ss->sec.isServer && IS_DTLS(ss)) {
ssl_CipherSpecReleaseByEpoch(ss, CipherSpecRead, TrafficKeyClearText); ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read, TrafficKeyClearText);
dtls_ReceivedFirstMessageInFlight(ss); dtls_ReceivedFirstMessageInFlight(ss);
} }
/* Process the context string */ /* Process the context string */
@ -2881,10 +3050,12 @@ tls13_HandleCertificate(sslSocket *ss, PRUint8 *b, PRUint32 length)
if (rv != SECSuccess) if (rv != SECSuccess)
return SECFailure; return SECFailure;
if (context.len) { if (ss->ssl3.clientCertRequested) {
/* The context string MUST be empty */ PORT_Assert(ss->sec.isServer);
FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CERTIFICATE, illegal_parameter); if (SECITEM_CompareItem(&context, &ss->xtnData.certReqContext) != 0) {
return SECFailure; FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CERTIFICATE, illegal_parameter);
return SECFailure;
}
} }
rv = ssl3_ConsumeHandshakeVariable(ss, &certList, 3, &b, &length); rv = ssl3_ConsumeHandshakeVariable(ss, &certList, 3, &b, &length);
@ -3125,6 +3296,25 @@ tls13_DeriveSecretWrap(sslSocket *ss, PK11SymKey *key,
return SECSuccess; 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. */ /* Derive traffic keys for the next cipher spec in the queue. */
static SECStatus static SECStatus
tls13_DeriveTrafficKeys(sslSocket *ss, ssl3CipherSpec *spec, 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 /* We want to keep read cipher specs around longer because
* there are cases where we might get either epoch N or * there are cases where we might get either epoch N or
* epoch N+1. */ * epoch N+1. */
if (IS_DTLS(ss) && spec->direction == CipherSpecRead) { if (IS_DTLS(ss) && spec->direction == ssl_secret_read) {
ssl_CipherSpecAddRef(spec); 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 /* 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. */ * record handling code can use the same checks for all versions. */
if (ssl3_ExtensionNegotiated(ss, ssl_record_size_limit_xtn)) { 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->opt.recordSizeLimit
: ss->xtnData.recordSizeLimit) - : ss->xtnData.recordSizeLimit) -
1; 1;
@ -3310,7 +3500,7 @@ tls13_SetAlertCipherSpec(sslSocket *ss)
} }
rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake, rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake,
CipherSpecWrite, PR_FALSE); ssl_secret_write, PR_FALSE);
if (rv != SECSuccess) { if (rv != SECSuccess) {
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
return SECFailure; return SECFailure;
@ -3325,7 +3515,7 @@ tls13_SetAlertCipherSpec(sslSocket *ss)
*/ */
static SECStatus static SECStatus
tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch, tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch,
CipherSpecDirection direction, PRBool deleteSecret) SSLSecretDirection direction, PRBool deleteSecret)
{ {
TrafficKeyType type; TrafficKeyType type;
SECStatus rv; SECStatus rv;
@ -3364,7 +3554,7 @@ tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch,
} }
/* Now that we've set almost everything up, finally cut over. */ /* 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_GetSpecWriteLock(ss);
ssl_CipherSpecRelease(*specp); /* May delete old cipher. */ ssl_CipherSpecRelease(*specp); /* May delete old cipher. */
*specp = spec; /* Overwrite. */ *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_TRC(3, ("%d: TLS13[%d]: %s installed key for epoch=%d (%s) dir=%s",
SSL_GETPID(), ss->fd, SSL_ROLE(ss), spec->epoch, SSL_GETPID(), ss->fd, SSL_ROLE(ss), spec->epoch,
spec->phase, SPEC_DIR(spec))); spec->phase, SPEC_DIR(spec)));
if (ss->ssl3.changedCipherSpecFunc) {
ss->ssl3.changedCipherSpecFunc(ss->ssl3.changedCipherSpecArg,
direction == CipherSpecWrite, spec);
}
return SECSuccess; return SECSuccess;
loser: 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); TLS13_SET_HS_STATE(ss, wait_finished);
return SECSuccess; 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_TRC(3, ("%d: TLS13[%d]: server handle finished handshake",
SSL_GETPID(), ss->fd)); 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); b, length);
if (rv != SECSuccess) { if (rv != SECSuccess) {
return SECFailure; return SECFailure;
} }
if (ss->firstHsDone) {
TLS13_SET_HS_STATE(ss, idle_handshake);
return SECSuccess;
}
if (!tls13_ShouldRequestClientAuth(ss) && if (!tls13_ShouldRequestClientAuth(ss) &&
(ss->ssl3.hs.zeroRttState != ssl_0rtt_done)) { (ss->ssl3.hs.zeroRttState != ssl_0rtt_done)) {
dtls_ReceivedFirstMessageInFlight(ss); dtls_ReceivedFirstMessageInFlight(ss);
} }
rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData, rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData,
CipherSpecRead, PR_FALSE); ssl_secret_read, PR_FALSE);
if (rv != SECSuccess) { if (rv != SECSuccess) {
FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
return SECFailure; return SECFailure;
} }
if (IS_DTLS(ss)) { 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 /* We need to keep the handshake cipher spec so we can
* read re-transmitted client Finished. */ * read re-transmitted client Finished. */
rv = dtls_StartTimer(ss, ss->ssl3.hs.hdTimer, 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) { if (rv != SECSuccess) {
return SECFailure; /* err code was set. */ return SECFailure; /* err code was set. */
} }
@ -4404,7 +4599,8 @@ tls13_SendClientSecondRound(sslSocket *ss)
" certificate authentication is still pending.", " certificate authentication is still pending.",
SSL_GETPID(), ss->fd)); SSL_GETPID(), ss->fd));
ss->ssl3.hs.restartTarget = tls13_SendClientSecondRound; ss->ssl3.hs.restartTarget = tls13_SendClientSecondRound;
return SECWouldBlock; PORT_SetError(PR_WOULD_BLOCK_ERROR);
return SECFailure;
} }
rv = tls13_ComputeApplicationSecrets(ss); rv = tls13_ComputeApplicationSecrets(ss);
@ -4432,14 +4628,14 @@ tls13_SendClientSecondRound(sslSocket *ss)
} }
rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake, rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake,
CipherSpecWrite, PR_FALSE); ssl_secret_write, PR_FALSE);
if (rv != SECSuccess) { if (rv != SECSuccess) {
FATAL_ERROR(ss, SSL_ERROR_INIT_CIPHER_SUITE_FAILURE, internal_error); FATAL_ERROR(ss, SSL_ERROR_INIT_CIPHER_SUITE_FAILURE, internal_error);
return SECFailure; return SECFailure;
} }
rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData, rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData,
CipherSpecRead, PR_FALSE); ssl_secret_read, PR_FALSE);
if (rv != SECSuccess) { if (rv != SECSuccess) {
FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
return SECFailure; return SECFailure;
@ -4457,7 +4653,7 @@ tls13_SendClientSecondRound(sslSocket *ss)
return SECFailure; return SECFailure;
} }
rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData, rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData,
CipherSpecWrite, PR_FALSE); ssl_secret_write, PR_FALSE);
if (rv != SECSuccess) { if (rv != SECSuccess) {
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
return SECFailure; return SECFailure;
@ -4815,7 +5011,8 @@ static const struct {
{ ssl_tls13_supported_versions_xtn, _M3(client_hello, server_hello, { ssl_tls13_supported_versions_xtn, _M3(client_hello, server_hello,
hello_retry_request) }, hello_retry_request) },
{ ssl_record_size_limit_xtn, _M2(client_hello, encrypted_extensions) }, { 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 tls13ExtensionStatus
@ -4924,7 +5121,7 @@ tls13_ProtectRecord(sslSocket *ss,
const int tagLen = cipher_def->tag_size; const int tagLen = cipher_def->tag_size;
SECStatus rv; 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_TRC(3, ("%d: TLS13[%d]: spec=%d epoch=%d (%s) protect 0x%0llx len=%u",
SSL_GETPID(), ss->fd, cwSpec, cwSpec->epoch, cwSpec->phase, SSL_GETPID(), ss->fd, cwSpec, cwSpec->epoch, cwSpec->phase,
cwSpec->nextSeqNum, contentLen)); cwSpec->nextSeqNum, contentLen));
@ -5018,7 +5215,7 @@ tls13_UnprotectRecord(sslSocket *ss,
*alert = bad_record_mac; /* Default alert for most issues. */ *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_TRC(3, ("%d: TLS13[%d]: spec=%d epoch=%d (%s) unprotect 0x%0llx len=%u",
SSL_GETPID(), ss->fd, spec, spec->epoch, spec->phase, SSL_GETPID(), ss->fd, spec, spec->epoch, spec->phase,
cText->seqNum, cText->buf->len)); cText->seqNum, cText->buf->len));
@ -5175,6 +5372,9 @@ tls13_MaybeDo0RTTHandshake(sslSocket *ss)
ss->ssl3.hs.zeroRttState = ssl_0rtt_sent; ss->ssl3.hs.zeroRttState = ssl_0rtt_sent;
ss->ssl3.hs.zeroRttSuite = ss->ssl3.hs.cipher_suite; 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)); 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); rv = tls13_DeriveEarlySecrets(ss);
if (rv != SECSuccess) { if (rv != SECSuccess) {
return SECFailure; return SECFailure;
@ -5216,7 +5413,7 @@ tls13_MaybeDo0RTTHandshake(sslSocket *ss)
ssl_CipherSpecAddRef(ss->ssl3.cwSpec); ssl_CipherSpecAddRef(ss->ssl3.cwSpec);
rv = tls13_SetCipherSpec(ss, TrafficKeyEarlyApplicationData, rv = tls13_SetCipherSpec(ss, TrafficKeyEarlyApplicationData,
CipherSpecWrite, PR_TRUE); ssl_secret_write, PR_TRUE);
if (rv != SECSuccess) { if (rv != SECSuccess) {
return SECFailure; 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, /* We shouldn't be getting any more early data, and if we do,
* it is because of reordering and we drop it. */ * it is because of reordering and we drop it. */
if (IS_DTLS(ss)) { if (IS_DTLS(ss)) {
ssl_CipherSpecReleaseByEpoch(ss, CipherSpecRead, ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read,
TrafficKeyEarlyApplicationData); TrafficKeyEarlyApplicationData);
dtls_ReceivedFirstMessageInFlight(ss); dtls_ReceivedFirstMessageInFlight(ss);
} }
@ -5292,7 +5489,7 @@ tls13_HandleEndOfEarlyData(sslSocket *ss, PRUint8 *b, PRUint32 length)
} }
rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake, rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake,
CipherSpecRead, PR_FALSE); ssl_secret_read, PR_FALSE);
if (rv != SECSuccess) { if (rv != SECSuccess) {
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
return SECFailure; return SECFailure;

View file

@ -131,6 +131,7 @@ SECStatus SSLExp_KeyUpdate(PRFileDesc *fd, PRBool requestUpdate);
PRBool tls13_MaybeTls13(sslSocket *ss); PRBool tls13_MaybeTls13(sslSocket *ss);
SSLAEADCipher tls13_GetAead(const ssl3BulkCipherDef *cipherDef); SSLAEADCipher tls13_GetAead(const ssl3BulkCipherDef *cipherDef);
void tls13_SetSpecRecordVersion(sslSocket *ss, ssl3CipherSpec *spec); void tls13_SetSpecRecordVersion(sslSocket *ss, ssl3CipherSpec *spec);
SECStatus SSLExp_SendCertificateRequest(PRFileDesc *fd);
/* Use this instead of FATAL_ERROR when no alert shall be sent. */ /* Use this instead of FATAL_ERROR when no alert shall be sent. */
#define LOG_ERROR(ss, prError) \ #define LOG_ERROR(ss, prError) \

View file

@ -915,6 +915,37 @@ tls13_ServerHandleCookieXtn(const sslSocket *ss, TLSExtensionData *xtnData,
return SECSuccess; 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; * enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode;
* *

View file

@ -93,5 +93,11 @@ SECStatus tls13_ClientSendEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData
SECStatus tls13_ServerHandleEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData, SECStatus tls13_ServerHandleEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData,
SECItem *data); SECItem *data);
SECStatus tls13_ClientCheckEsniXtn(sslSocket *ss); 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 #endif

View file

@ -168,6 +168,7 @@
'cmd/pk11ectest/pk11ectest.gyp:pk11ectest', 'cmd/pk11ectest/pk11ectest.gyp:pk11ectest',
'cmd/pk11gcmtest/pk11gcmtest.gyp:pk11gcmtest', 'cmd/pk11gcmtest/pk11gcmtest.gyp:pk11gcmtest',
'cmd/pk11mode/pk11mode.gyp:pk11mode', 'cmd/pk11mode/pk11mode.gyp:pk11mode',
'cmd/pk11importtest/pk11importtest.gyp:pk11importtest',
'cmd/pk1sign/pk1sign.gyp:pk1sign', 'cmd/pk1sign/pk1sign.gyp:pk1sign',
'cmd/pp/pp.gyp:pp', 'cmd/pp/pp.gyp:pp',
'cmd/rsaperf/rsaperf.gyp:rsaperf', 'cmd/rsaperf/rsaperf.gyp:rsaperf',

View file

@ -252,7 +252,15 @@ dbtest_main()
else else
html_passed "Nicknane conflict test-setting nickname conflict was correctly rejected" html_passed "Nicknane conflict test-setting nickname conflict was correctly rejected"
fi 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 ################################################# ################## main #################################################

View file

@ -23,6 +23,7 @@
gtest_init() gtest_init()
{ {
cd "$(dirname "$1")" cd "$(dirname "$1")"
SOURCE_DIR="$PWD"/../..
if [ -z "${INIT_SOURCED}" -o "${INIT_SOURCED}" != "TRUE" ]; then if [ -z "${INIT_SOURCED}" -o "${INIT_SOURCED}" != "TRUE" ]; then
cd ../common cd ../common
. ./init.sh . ./init.sh
@ -33,6 +34,7 @@ gtest_init()
if [ -z "${CLEANUP}" ] ; then # if nobody else is responsible for if [ -z "${CLEANUP}" ] ; then # if nobody else is responsible for
CLEANUP="${SCRIPTNAME}" # cleaning this script will do it CLEANUP="${SCRIPTNAME}" # cleaning this script will do it
fi fi
} }
########################## gtest_start ############################# ########################## gtest_start #############################
@ -42,7 +44,7 @@ gtest_start()
{ {
echo "gtests: ${GTESTS}" echo "gtests: ${GTESTS}"
for i in ${GTESTS}; do for i in ${GTESTS}; do
if [ ! -f ${BINDIR}/$i ]; then if [ ! -f "${BINDIR}/$i" ]; then
html_unknown "Skipping $i (not built)" html_unknown "Skipping $i (not built)"
continue continue
fi fi
@ -50,20 +52,22 @@ gtest_start()
html_head "$i" html_head "$i"
if [ ! -d "$GTESTDIR" ]; then if [ ! -d "$GTESTDIR" ]; then
mkdir -p "$GTESTDIR" mkdir -p "$GTESTDIR"
echo "${BINDIR}/certutil" -N -d "$GTESTDIR" --empty-password 2>&1
"${BINDIR}/certutil" -N -d "$GTESTDIR" --empty-password 2>&1
fi fi
cd "$GTESTDIR" cd "$GTESTDIR"
GTESTREPORT="$GTESTDIR/report.xml" GTESTREPORT="$GTESTDIR/report.xml"
PARSED_REPORT="$GTESTDIR/report.parsed" PARSED_REPORT="$GTESTDIR/report.parsed"
echo "executing $i" echo "executing $i"
${BINDIR}/$i "${SOURCE_DIR}/gtests/freebl_gtest/kat/Hash_DRBG.rsp" \ "${BINDIR}/$i" "${SOURCE_DIR}/gtests/freebl_gtest/kat/Hash_DRBG.rsp" \
-d "$GTESTDIR" --gtest_output=xml:"${GTESTREPORT}" \ -d "$GTESTDIR" -w --gtest_output=xml:"${GTESTREPORT}" \
--gtest_filter="${GTESTFILTER-*}" --gtest_filter="${GTESTFILTER:-*}"
html_msg $? 0 "$i run successfully" html_msg $? 0 "$i run successfully"
echo "test output dir: ${GTESTREPORT}" echo "test output dir: ${GTESTREPORT}"
echo "executing sed to parse the xml report" 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" 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 if [ "$result" = "notrun" ]; then
echo "$name" SKIPPED echo "$name" SKIPPED
elif [ "$result" = "run" ]; then elif [ "$result" = "run" ]; then
@ -78,13 +82,12 @@ gtest_start()
gtest_cleanup() gtest_cleanup()
{ {
html "</TABLE><BR>" html "</TABLE><BR>"
cd ${QADIR} cd "${QADIR}"
. common/cleanup.sh . common/cleanup.sh
} }
################## main ################################################# ################## main #################################################
GTESTS="prng_gtest certhigh_gtest certdb_gtest der_gtest pk11_gtest util_gtest freebl_gtest softoken_gtest sysinit_gtest blake2b_gtest" GTESTS="${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"
gtest_init $0
gtest_start gtest_start
gtest_cleanup gtest_cleanup

View file

@ -43,25 +43,25 @@ let prompterParent = runInParent(() => {
let prompter2 = promptFac.getPrompt(chromeWin, Ci.nsIAuthPrompt2); let prompter2 = promptFac.getPrompt(chromeWin, Ci.nsIAuthPrompt2);
let channels = {}; let channels = {};
channels.channel1 = Services.io.newChannel2("http://example.com", channels.channel1 = Services.io.newChannel("http://example.com",
null, null,
null, null,
null, // aLoadingNode null, // aLoadingNode
Services. Services.
scriptSecurityManager.getSystemPrincipal(), scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER); Ci.nsIContentPolicy.TYPE_OTHER);
channels.channel2 = Services.io.newChannel2("http://example2.com", channels.channel2 = Services.io.newChannel("http://example2.com",
null, null,
null, null,
null, // aLoadingNode null, // aLoadingNode
Services. Services.
scriptSecurityManager.getSystemPrincipal(), scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER); Ci.nsIContentPolicy.TYPE_OTHER);
addMessageListener("proxyPrompter", function onMessage(msg) { addMessageListener("proxyPrompter", function onMessage(msg) {
let args = [...msg.args]; let args = [...msg.args];

View file

@ -103,14 +103,14 @@ var resolveCallback = SpecialPowers.wrapCallbackObject({
// The proxyChannel needs to move to at least on-modify-request to // The proxyChannel needs to move to at least on-modify-request to
// have valid ProxyInfo, but we use OnStartRequest during startup() // have valid ProxyInfo, but we use OnStartRequest during startup()
// for simplicity. // for simplicity.
proxyChannel = SpecialPowers.Services.io.newChannel2(proxiedHost, proxyChannel = SpecialPowers.Services.io.newChannel(proxiedHost,
null, null,
null, null,
null, // aLoadingNode null, // aLoadingNode
systemPrincipal, systemPrincipal,
null, // aTriggeringPrincipal null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER); Ci.nsIContentPolicy.TYPE_OTHER);
proxyChannel.asyncOpen(SpecialPowers.wrapCallbackObject(new proxyChannelListener())); 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. // 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 pps = SpecialPowers.Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
var channel = SpecialPowers.Services.io.newChannel2("http://example.com", var channel = SpecialPowers.Services.io.newChannel("http://example.com",
null, null,
null, null,
null, // aLoadingNode null, // aLoadingNode
systemPrincipal, systemPrincipal,
null, // aTriggeringPrincipal null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER); Ci.nsIContentPolicy.TYPE_OTHER);
pps.asyncResolve(channel, 0, resolveCallback); pps.asyncResolve(channel, 0, resolveCallback);
} }

View file

@ -1584,12 +1584,12 @@ nsresult PendingLookup::SendRemoteQueryInternal(Reason& aReason) {
// Set up the channel to transmit the request to the service. // Set up the channel to transmit the request to the service.
nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
rv = ios->NewChannel2(serviceUrl, nullptr, nullptr, rv = ios->NewChannel(serviceUrl, nullptr, nullptr,
nullptr, // aLoadingNode nullptr, // aLoadingNode
nsContentUtils::GetSystemPrincipal(), nsContentUtils::GetSystemPrincipal(),
nullptr, // aTriggeringPrincipal nullptr, // aTriggeringPrincipal
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
nsIContentPolicy::TYPE_OTHER, getter_AddRefs(mChannel)); nsIContentPolicy::TYPE_OTHER, getter_AddRefs(mChannel));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();

View file

@ -735,12 +735,12 @@ function makeURI(aURLSpec, aCharset) {
function makeChannel(url) { function makeChannel(url) {
try { try {
let uri = typeof url == "string" ? Services.io.newURI(url) : url; let uri = typeof url == "string" ? Services.io.newURI(url) : url;
return Services.io.newChannelFromURI2(uri, return Services.io.newChannelFromURI(uri,
null, /* loadingNode */ null, /* loadingNode */
Services.scriptSecurityManager.getSystemPrincipal(), Services.scriptSecurityManager.getSystemPrincipal(),
null, /* triggeringPrincipal */ null, /* triggeringPrincipal */
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER); Ci.nsIContentPolicy.TYPE_OTHER);
} catch (ex) { } } catch (ex) { }
return null; return null;