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 {
// Use the passed in resolvedURI if we have one
const resolvedURI = aResolvedURI || Services.io.newChannelFromURI2(
const resolvedURI = aResolvedURI || Services.io.newChannelFromURI(
aURI,
null, // loadingNode
Services.scriptSecurityManager.getSystemPrincipal(), // loadingPrincipal

View file

@ -1591,7 +1591,7 @@ var SessionStoreInternal = {
onQuitApplicationGranted: function ssi_onQuitApplicationGranted(syncShutdown = false) {
// Collect an initial snapshot of window data before we do the flush.
let index = 0;
for (let window of this._browserWindows) {
for (let window of this._orderedBrowserWindows) {
this._collectWindowData(window);
this._windows[window.__SSi].zIndex = ++index;
}
@ -3416,7 +3416,7 @@ var SessionStoreInternal = {
if (RunState.isRunning) {
// update the data for all windows with activities since the last save operation.
let index = 0;
for (let window of this._browserWindows) {
for (let window of this._orderedBrowserWindows) {
if (!this._isWindowLoaded(window)) // window data is still in _statesToRestore
continue;
if (aUpdateAll || DirtyWindows.has(window) || window == activeWindow) {
@ -4527,8 +4527,10 @@ var SessionStoreInternal = {
},
/**
* Iterator that yields all currently opened browser windows, in order.
* Iterator that yields all currently opened browser windows.
* (Might miss the most recent one.)
* This list is in focus order, but may include minimized windows
* before non-minimized windows.
*/
_browserWindows: {
* [Symbol.iterator]() {
@ -4539,6 +4541,30 @@ var SessionStoreInternal = {
},
},
/**
* Iterator that yields all currently opened browser windows,
* with minimized windows last.
* (Might miss the most recent window.)
*/
_orderedBrowserWindows: {
* [Symbol.iterator]() {
let windows = BrowserWindowTracker.orderedWindows;
windows.sort((a, b) => {
if (a.windowState == a.STATE_MINIMIZED && b.windowState != b.STATE_MINIMIZED) {
return 1;
}
if (a.windowState != a.STATE_MINIMIZED && b.windowState == b.STATE_MINIMIZED) {
return -1;
}
return 0;
});
for (let window of windows) {
if (window.__SSi && !window.closed)
yield window;
}
},
},
/**
* Returns most recent window
* @returns Window reference

View file

@ -19,7 +19,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
// Constants
const TAB_EVENTS = ["TabBrowserInserted", "TabSelect"];
const WINDOW_EVENTS = ["activate", "sizemodechange", "unload"];
const WINDOW_EVENTS = ["activate", "unload"];
const DEBUG = false;
// Variables
@ -64,9 +64,6 @@ function _handleEvent(event) {
case "activate":
WindowHelper.onActivate(event.target);
break;
case "sizemodechange":
WindowHelper.onSizemodeChange(event.target);
break;
case "unload":
WindowHelper.removeWindow(event.currentTarget);
break;
@ -144,14 +141,6 @@ var WindowHelper = {
_updateCurrentContentOuterWindowID(window.gBrowser.selectedBrowser);
},
onSizemodeChange(window) {
if (window.windowState == window.STATE_MINIMIZED) {
// Make sure to have the minimized window behind unminimized windows.
_untrackWindowOrder(window);
_trackWindowOrder(window);
}
},
};
this.BrowserWindowTracker = {

View file

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

View file

@ -110,16 +110,5 @@ add_task(async function test_orderedWindows() {
let expected = [1, 6, 4, 9, 8, 7, 5, 3, 2, 0];
Assert.deepEqual(expected, ordered2.map(w => windows.indexOf(w)),
"After shuffle of focused windows, the order should've changed.");
// Minimizing a window should put it at the end of the ordered list of windows.
let promise = BrowserTestUtils.waitForEvent(windows[9], "sizemodechange");
windows[9].minimize();
await promise;
let ordered3 = BrowserWindowTracker.orderedWindows.filter(w => w != TEST_WINDOW);
// Test the end of the array of window indices, because Windows Debug builds
// mysteriously swap the order of the first two windows.
Assert.deepEqual([8, 7, 5, 3, 2, 0, 9], ordered3.map(w => windows.indexOf(w)).slice(3),
"When a window is minimized, the order should've changed.");
});
});

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, "MarkupView", "devtools/client/inspector/markup/markup");
loader.lazyRequireGetter(this, "HighlightersOverlay", "devtools/client/inspector/shared/highlighters-overlay");
loader.lazyRequireGetter(this, "nodeConstants", "devtools/shared/dom-node-constants");
loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
loader.lazyRequireGetter(this, "ExtensionSidebar", "devtools/client/inspector/extensions/extension-sidebar");
loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
loader.lazyRequireGetter(this, "openContentLink", "devtools/client/shared/link", true);
loader.lazyRequireGetter(this, "saveScreenshot", "devtools/shared/screenshot/save");
loader.lazyImporter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
@ -41,9 +36,6 @@ loader.lazyImporter(this, "DeferredTask", "resource://gre/modules/DeferredTask.j
const {LocalizationHelper, localizeMarkup} = require("devtools/shared/l10n");
const INSPECTOR_L10N =
new LocalizationHelper("devtools/client/locales/inspector.properties");
loader.lazyGetter(this, "TOOLBOX_L10N", function() {
return new LocalizationHelper("devtools/client/locales/toolbox.properties");
});
// Sidebar dimensions
const INITIAL_SIDEBAR_SIZE = 350;
@ -125,12 +117,9 @@ function Inspector(toolbox) {
// telemetry counts in the Grid Inspector are not double counted on reload.
this.previousURL = this.target.url;
this.nodeMenuTriggerInfo = null;
this._clearSearchResultsLabel = this._clearSearchResultsLabel.bind(this);
this._handleRejectionIfNotDestroyed = this._handleRejectionIfNotDestroyed.bind(this);
this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
this._onContextMenu = this._onContextMenu.bind(this);
this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this);
@ -1402,8 +1391,6 @@ Inspector.prototype = {
if (this._markupFrame) {
this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
this._markupFrame.contentWindow.removeEventListener("contextmenu",
this._onContextMenu);
}
if (this._search) {
@ -1449,449 +1436,6 @@ Inspector.prototype = {
return this._panelDestroyer;
},
/**
* Returns the clipboard content if it is appropriate for pasting
* into the current node's outer HTML, otherwise returns null.
*/
_getClipboardContentForPaste: function() {
const content = clipboardHelper.getText();
if (content && content.trim().length > 0) {
return content;
}
return null;
},
_onContextMenu: function(e) {
if (!(e.originalTarget instanceof Element) ||
e.originalTarget.closest("input[type=text]") ||
e.originalTarget.closest("input:not([type])") ||
e.originalTarget.closest("textarea")) {
return;
}
e.stopPropagation();
e.preventDefault();
this._openMenu({
screenX: e.screenX,
screenY: e.screenY,
target: e.target,
});
},
_openMenu: function({ target, screenX = 0, screenY = 0 } = { }) {
if (this.selection.isSlotted()) {
// Slotted elements should not show any context menu.
return null;
}
const markupContainer = this.markup.getContainer(this.selection.nodeFront);
this.contextMenuTarget = target;
this.nodeMenuTriggerInfo = markupContainer &&
markupContainer.editor.getInfoAtNode(target);
const isSelectionElement = this.selection.isElementNode() &&
!this.selection.isPseudoElementNode();
const isEditableElement = isSelectionElement &&
!this.selection.isAnonymousNode();
const isDuplicatableElement = isSelectionElement &&
!this.selection.isAnonymousNode() &&
!this.selection.isRoot();
const isScreenshotable = isSelectionElement &&
this.selection.nodeFront.isTreeDisplayed;
const menu = new Menu();
menu.append(new MenuItem({
id: "node-menu-edithtml",
label: INSPECTOR_L10N.getStr("inspectorHTMLEdit.label"),
accesskey: INSPECTOR_L10N.getStr("inspectorHTMLEdit.accesskey"),
disabled: !isEditableElement,
click: () => this.editHTML(),
}));
menu.append(new MenuItem({
id: "node-menu-add",
label: INSPECTOR_L10N.getStr("inspectorAddNode.label"),
accesskey: INSPECTOR_L10N.getStr("inspectorAddNode.accesskey"),
disabled: !this.canAddHTMLChild(),
click: () => this.addNode(),
}));
menu.append(new MenuItem({
id: "node-menu-duplicatenode",
label: INSPECTOR_L10N.getStr("inspectorDuplicateNode.label"),
disabled: !isDuplicatableElement,
click: () => this.duplicateNode(),
}));
menu.append(new MenuItem({
id: "node-menu-delete",
label: INSPECTOR_L10N.getStr("inspectorHTMLDelete.label"),
accesskey: INSPECTOR_L10N.getStr("inspectorHTMLDelete.accesskey"),
disabled: !this.isDeletable(this.selection.nodeFront),
click: () => this.deleteNode(),
}));
menu.append(new MenuItem({
label: INSPECTOR_L10N.getStr("inspectorAttributesSubmenu.label"),
accesskey:
INSPECTOR_L10N.getStr("inspectorAttributesSubmenu.accesskey"),
submenu: this._getAttributesSubmenu(isEditableElement),
}));
menu.append(new MenuItem({
type: "separator",
}));
// Set the pseudo classes
for (const name of ["hover", "active", "focus", "focus-within"]) {
const menuitem = new MenuItem({
id: "node-menu-pseudo-" + name,
label: name,
type: "checkbox",
click: this.togglePseudoClass.bind(this, ":" + name),
});
if (isSelectionElement) {
const checked = this.selection.nodeFront.hasPseudoClassLock(":" + name);
menuitem.checked = checked;
} else {
menuitem.disabled = true;
}
menu.append(menuitem);
}
menu.append(new MenuItem({
type: "separator",
}));
menu.append(new MenuItem({
label: INSPECTOR_L10N.getStr("inspectorCopyHTMLSubmenu.label"),
submenu: this._getCopySubmenu(markupContainer, isSelectionElement),
}));
menu.append(new MenuItem({
label: INSPECTOR_L10N.getStr("inspectorPasteHTMLSubmenu.label"),
submenu: this._getPasteSubmenu(isEditableElement),
}));
menu.append(new MenuItem({
type: "separator",
}));
const isNodeWithChildren = this.selection.isNode() &&
markupContainer.hasChildren;
menu.append(new MenuItem({
id: "node-menu-expand",
label: INSPECTOR_L10N.getStr("inspectorExpandNode.label"),
disabled: !isNodeWithChildren,
click: () => this.expandNode(),
}));
menu.append(new MenuItem({
id: "node-menu-collapse",
label: INSPECTOR_L10N.getStr("inspectorCollapseAll.label"),
disabled: !isNodeWithChildren || !markupContainer.expanded,
click: () => this.collapseAll(),
}));
menu.append(new MenuItem({
type: "separator",
}));
menu.append(new MenuItem({
id: "node-menu-scrollnodeintoview",
label: INSPECTOR_L10N.getStr("inspectorScrollNodeIntoView.label"),
accesskey:
INSPECTOR_L10N.getStr("inspectorScrollNodeIntoView.accesskey"),
disabled: !isSelectionElement,
click: () => this.scrollNodeIntoView(),
}));
menu.append(new MenuItem({
id: "node-menu-screenshotnode",
label: INSPECTOR_L10N.getStr("inspectorScreenshotNode.label"),
disabled: !isScreenshotable,
click: () => this.screenshotNode().catch(console.error),
}));
menu.append(new MenuItem({
id: "node-menu-useinconsole",
label: INSPECTOR_L10N.getStr("inspectorUseInConsole.label"),
click: () => this.useInConsole(),
}));
menu.append(new MenuItem({
id: "node-menu-showdomproperties",
label: INSPECTOR_L10N.getStr("inspectorShowDOMProperties.label"),
click: () => this.showDOMProperties(),
}));
if (this.selection.nodeFront.customElementLocation) {
menu.append(new MenuItem({
type: "separator",
}));
menu.append(new MenuItem({
id: "node-menu-jumptodefinition",
label: INSPECTOR_L10N.getStr("inspectorCustomElementDefinition.label"),
click: () => this.jumpToCustomElementDefinition(),
}));
}
this.buildA11YMenuItem(menu);
const nodeLinkMenuItems = this._getNodeLinkMenuItems();
if (nodeLinkMenuItems.filter(item => item.visible).length > 0) {
menu.append(new MenuItem({
id: "node-menu-link-separator",
type: "separator",
}));
}
for (const menuitem of nodeLinkMenuItems) {
menu.append(menuitem);
}
menu.popup(screenX, screenY, this._toolbox);
return menu;
},
buildA11YMenuItem: function(menu) {
if (!(this.selection.isElementNode() || this.selection.isTextNode()) ||
!Services.prefs.getBoolPref("devtools.accessibility.enabled")) {
return;
}
const showA11YPropsItem = new MenuItem({
id: "node-menu-showaccessibilityproperties",
label: INSPECTOR_L10N.getStr("inspectorShowAccessibilityProperties.label"),
click: () => this.showAccessibilityProperties(),
disabled: true,
});
// Only attempt to determine if a11y props menu item needs to be enabled if
// AccessibilityFront is enabled.
if (this.accessibilityFront.enabled) {
this._updateA11YMenuItem(showA11YPropsItem);
}
menu.append(showA11YPropsItem);
},
_updateA11YMenuItem: async function(menuItem) {
const hasMethod = await this.target.actorHasMethod("domwalker",
"hasAccessibilityProperties");
if (!hasMethod) {
return;
}
const hasA11YProps = await this.walker.hasAccessibilityProperties(
this.selection.nodeFront);
if (hasA11YProps) {
this._toolbox.doc.getElementById(menuItem.id).disabled = menuItem.disabled = false;
}
this.emit("node-menu-updated");
},
_getCopySubmenu: function(markupContainer, isSelectionElement) {
const copySubmenu = new Menu();
copySubmenu.append(new MenuItem({
id: "node-menu-copyinner",
label: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.label"),
accesskey: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.accesskey"),
disabled: !isSelectionElement,
click: () => this.copyInnerHTML(),
}));
copySubmenu.append(new MenuItem({
id: "node-menu-copyouter",
label: INSPECTOR_L10N.getStr("inspectorCopyOuterHTML.label"),
accesskey: INSPECTOR_L10N.getStr("inspectorCopyOuterHTML.accesskey"),
disabled: !isSelectionElement,
click: () => this.copyOuterHTML(),
}));
copySubmenu.append(new MenuItem({
id: "node-menu-copyuniqueselector",
label: INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.label"),
accesskey:
INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.accesskey"),
disabled: !isSelectionElement,
click: () => this.copyUniqueSelector(),
}));
copySubmenu.append(new MenuItem({
id: "node-menu-copycsspath",
label: INSPECTOR_L10N.getStr("inspectorCopyCSSPath.label"),
accesskey:
INSPECTOR_L10N.getStr("inspectorCopyCSSPath.accesskey"),
disabled: !isSelectionElement,
click: () => this.copyCssPath(),
}));
copySubmenu.append(new MenuItem({
id: "node-menu-copyxpath",
label: INSPECTOR_L10N.getStr("inspectorCopyXPath.label"),
accesskey:
INSPECTOR_L10N.getStr("inspectorCopyXPath.accesskey"),
disabled: !isSelectionElement,
click: () => this.copyXPath(),
}));
copySubmenu.append(new MenuItem({
id: "node-menu-copyimagedatauri",
label: INSPECTOR_L10N.getStr("inspectorImageDataUri.label"),
disabled: !isSelectionElement || !markupContainer ||
!markupContainer.isPreviewable(),
click: () => this.copyImageDataUri(),
}));
return copySubmenu;
},
_getPasteSubmenu: function(isEditableElement) {
const isPasteable = isEditableElement && this._getClipboardContentForPaste();
const disableAdjacentPaste = !isPasteable || this.selection.isRoot() ||
this.selection.isBodyNode() || this.selection.isHeadNode();
const disableFirstLastPaste = !isPasteable ||
(this.selection.isHTMLNode() && this.selection.isRoot());
const pasteSubmenu = new Menu();
pasteSubmenu.append(new MenuItem({
id: "node-menu-pasteinnerhtml",
label: INSPECTOR_L10N.getStr("inspectorPasteInnerHTML.label"),
accesskey: INSPECTOR_L10N.getStr("inspectorPasteInnerHTML.accesskey"),
disabled: !isPasteable,
click: () => this.pasteInnerHTML(),
}));
pasteSubmenu.append(new MenuItem({
id: "node-menu-pasteouterhtml",
label: INSPECTOR_L10N.getStr("inspectorPasteOuterHTML.label"),
accesskey: INSPECTOR_L10N.getStr("inspectorPasteOuterHTML.accesskey"),
disabled: !isPasteable,
click: () => this.pasteOuterHTML(),
}));
pasteSubmenu.append(new MenuItem({
id: "node-menu-pastebefore",
label: INSPECTOR_L10N.getStr("inspectorHTMLPasteBefore.label"),
accesskey:
INSPECTOR_L10N.getStr("inspectorHTMLPasteBefore.accesskey"),
disabled: disableAdjacentPaste,
click: () => this.pasteAdjacentHTML("beforeBegin"),
}));
pasteSubmenu.append(new MenuItem({
id: "node-menu-pasteafter",
label: INSPECTOR_L10N.getStr("inspectorHTMLPasteAfter.label"),
accesskey:
INSPECTOR_L10N.getStr("inspectorHTMLPasteAfter.accesskey"),
disabled: disableAdjacentPaste,
click: () => this.pasteAdjacentHTML("afterEnd"),
}));
pasteSubmenu.append(new MenuItem({
id: "node-menu-pastefirstchild",
label: INSPECTOR_L10N.getStr("inspectorHTMLPasteFirstChild.label"),
accesskey:
INSPECTOR_L10N.getStr("inspectorHTMLPasteFirstChild.accesskey"),
disabled: disableFirstLastPaste,
click: () => this.pasteAdjacentHTML("afterBegin"),
}));
pasteSubmenu.append(new MenuItem({
id: "node-menu-pastelastchild",
label: INSPECTOR_L10N.getStr("inspectorHTMLPasteLastChild.label"),
accesskey:
INSPECTOR_L10N.getStr("inspectorHTMLPasteLastChild.accesskey"),
disabled: disableFirstLastPaste,
click: () => this.pasteAdjacentHTML("beforeEnd"),
}));
return pasteSubmenu;
},
_getAttributesSubmenu: function(isEditableElement) {
const attributesSubmenu = new Menu();
const nodeInfo = this.nodeMenuTriggerInfo;
const isAttributeClicked = isEditableElement && nodeInfo &&
nodeInfo.type === "attribute";
attributesSubmenu.append(new MenuItem({
id: "node-menu-add-attribute",
label: INSPECTOR_L10N.getStr("inspectorAddAttribute.label"),
accesskey: INSPECTOR_L10N.getStr("inspectorAddAttribute.accesskey"),
disabled: !isEditableElement,
click: () => this.onAddAttribute(),
}));
attributesSubmenu.append(new MenuItem({
id: "node-menu-copy-attribute",
label: INSPECTOR_L10N.getFormatStr("inspectorCopyAttributeValue.label",
isAttributeClicked ? `${nodeInfo.value}` : ""),
accesskey: INSPECTOR_L10N.getStr("inspectorCopyAttributeValue.accesskey"),
disabled: !isAttributeClicked,
click: () => this.onCopyAttributeValue(),
}));
attributesSubmenu.append(new MenuItem({
id: "node-menu-edit-attribute",
label: INSPECTOR_L10N.getFormatStr("inspectorEditAttribute.label",
isAttributeClicked ? `${nodeInfo.name}` : ""),
accesskey: INSPECTOR_L10N.getStr("inspectorEditAttribute.accesskey"),
disabled: !isAttributeClicked,
click: () => this.onEditAttribute(),
}));
attributesSubmenu.append(new MenuItem({
id: "node-menu-remove-attribute",
label: INSPECTOR_L10N.getFormatStr("inspectorRemoveAttribute.label",
isAttributeClicked ? `${nodeInfo.name}` : ""),
accesskey: INSPECTOR_L10N.getStr("inspectorRemoveAttribute.accesskey"),
disabled: !isAttributeClicked,
click: () => this.onRemoveAttribute(),
}));
return attributesSubmenu;
},
/**
* Link menu items can be shown or hidden depending on the context and
* selected node, and their labels can vary.
*
* @return {Array} list of visible menu items related to links.
*/
_getNodeLinkMenuItems: function() {
const linkFollow = new MenuItem({
id: "node-menu-link-follow",
visible: false,
click: () => this.onFollowLink(),
});
const linkCopy = new MenuItem({
id: "node-menu-link-copy",
visible: false,
click: () => this.onCopyLink(),
});
// Get information about the right-clicked node.
const popupNode = this.contextMenuTarget;
if (!popupNode || !popupNode.classList.contains("link")) {
return [linkFollow, linkCopy];
}
const type = popupNode.dataset.type;
if ((type === "uri" || type === "cssresource" || type === "jsresource")) {
// Links can't be opened in new tabs in the browser toolbox.
if (type === "uri" && !this.target.chrome) {
linkFollow.visible = true;
linkFollow.label = INSPECTOR_L10N.getStr(
"inspector.menu.openUrlInNewTab.label");
} else if (type === "cssresource") {
linkFollow.visible = true;
linkFollow.label = TOOLBOX_L10N.getStr(
"toolbox.viewCssSourceInStyleEditor.label");
} else if (type === "jsresource") {
linkFollow.visible = true;
linkFollow.label = TOOLBOX_L10N.getStr(
"toolbox.viewJsSourceInDebugger.label");
}
linkCopy.visible = true;
linkCopy.label = INSPECTOR_L10N.getStr(
"inspector.menu.copyUrlToClipboard.label");
} else if (type === "idref") {
linkFollow.visible = true;
linkFollow.label = INSPECTOR_L10N.getFormatStr(
"inspector.menu.selectElement.label", popupNode.dataset.link);
}
return [linkFollow, linkCopy];
},
_initMarkup: function() {
if (!this._markupFrame) {
this._markupFrame = this.panelDoc.createElement("iframe");
@ -1913,7 +1457,6 @@ Inspector.prototype = {
_onMarkupFrameLoad: function() {
this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
this._markupFrame.contentWindow.addEventListener("contextmenu", this._onContextMenu);
this._markupFrame.contentWindow.focus();
this._markupBox.style.visibility = "visible";
this.markup = new MarkupView(this, this._markupFrame, this._toolbox.win);
@ -2028,241 +1571,6 @@ Inspector.prototype = {
return promise.resolve();
},
/**
* Show DOM properties
*/
showDOMProperties: function() {
this._toolbox.openSplitConsole().then(() => {
const panel = this._toolbox.getPanel("webconsole");
const jsterm = panel.hud.jsterm;
jsterm.execute("inspect($0)");
jsterm.focus();
});
},
jumpToCustomElementDefinition: function() {
const node = this.selection.nodeFront;
const { url, line } = node.customElementLocation;
this._toolbox.viewSourceInDebugger(url, line, "show_custom_element");
},
/**
* Show Accessibility properties for currently selected node
*/
async showAccessibilityProperties() {
const a11yPanel = await this._toolbox.selectTool("accessibility");
// Select the accessible object in the panel and wait for the event that
// tells us it has been done.
const onSelected = a11yPanel.once("new-accessible-front-selected");
a11yPanel.selectAccessibleForNode(this.selection.nodeFront,
"inspector-context-menu");
await onSelected;
},
/**
* Use in Console.
*
* Takes the currently selected node in the inspector and assigns it to a
* temp variable on the content window. Also opens the split console and
* autofills it with the temp variable.
*/
useInConsole: function() {
this._toolbox.openSplitConsole().then(() => {
const panel = this._toolbox.getPanel("webconsole");
const jsterm = panel.hud.jsterm;
const evalString = `{ let i = 0;
while (window.hasOwnProperty("temp" + i) && i < 1000) {
i++;
}
window["temp" + i] = $0;
"temp" + i;
}`;
const options = {
selectedNodeActor: this.selection.nodeFront.actorID,
};
jsterm.requestEvaluation(evalString, options).then((res) => {
jsterm.setInputValue(res.result);
this.emit("console-var-ready");
});
});
},
/**
* Edit the outerHTML of the selected Node.
*/
editHTML: function() {
if (!this.selection.isNode()) {
return;
}
if (this.markup) {
this.markup.beginEditingOuterHTML(this.selection.nodeFront);
}
},
/**
* Paste the contents of the clipboard into the selected Node's outer HTML.
*/
pasteOuterHTML: function() {
const content = this._getClipboardContentForPaste();
if (!content) {
return promise.reject("No clipboard content for paste");
}
const node = this.selection.nodeFront;
return this.markup.getNodeOuterHTML(node).then(oldContent => {
this.markup.updateNodeOuterHTML(node, content, oldContent);
});
},
/**
* Paste the contents of the clipboard into the selected Node's inner HTML.
*/
pasteInnerHTML: function() {
const content = this._getClipboardContentForPaste();
if (!content) {
return promise.reject("No clipboard content for paste");
}
const node = this.selection.nodeFront;
return this.markup.getNodeInnerHTML(node).then(oldContent => {
this.markup.updateNodeInnerHTML(node, content, oldContent);
});
},
/**
* Paste the contents of the clipboard as adjacent HTML to the selected Node.
* @param position
* The position as specified for Element.insertAdjacentHTML
* (i.e. "beforeBegin", "afterBegin", "beforeEnd", "afterEnd").
*/
pasteAdjacentHTML: function(position) {
const content = this._getClipboardContentForPaste();
if (!content) {
return promise.reject("No clipboard content for paste");
}
const node = this.selection.nodeFront;
return this.markup.insertAdjacentHTMLToNode(node, position, content);
},
/**
* Copy the innerHTML of the selected Node to the clipboard.
*/
copyInnerHTML: function() {
if (!this.selection.isNode()) {
return;
}
this._copyLongString(this.walker.innerHTML(this.selection.nodeFront));
},
/**
* Copy the outerHTML of the selected Node to the clipboard.
*/
copyOuterHTML: function() {
if (!this.selection.isNode()) {
return;
}
const node = this.selection.nodeFront;
switch (node.nodeType) {
case nodeConstants.ELEMENT_NODE :
this._copyLongString(this.walker.outerHTML(node));
break;
case nodeConstants.COMMENT_NODE :
this._getLongString(node.getNodeValue()).then(comment => {
clipboardHelper.copyString("<!--" + 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.
*/
@ -2285,160 +1593,6 @@ Inspector.prototype = {
await saveScreenshot(this.panelWin, args, screenshot);
},
/**
* Scroll the node into view.
*/
scrollNodeIntoView: function() {
if (!this.selection.isNode()) {
return;
}
this.selection.nodeFront.scrollIntoView();
},
/**
* Duplicate the selected node
*/
duplicateNode: function() {
const selection = this.selection;
if (!selection.isElementNode() ||
selection.isRoot() ||
selection.isAnonymousNode() ||
selection.isPseudoElementNode()) {
return;
}
this.walker.duplicateNode(selection.nodeFront).catch(console.error);
},
/**
* Delete the selected node.
*/
deleteNode: function() {
if (!this.selection.isNode() ||
this.selection.isRoot()) {
return;
}
// If the markup panel is active, use the markup panel to delete
// the node, making this an undoable action.
if (this.markup) {
this.markup.deleteNode(this.selection.nodeFront);
} else {
// remove the node from content
this.walker.removeNode(this.selection.nodeFront);
}
},
/**
* Add attribute to node.
* Used for node context menu and shouldn't be called directly.
*/
onAddAttribute: function() {
const container = this.markup.getContainer(this.selection.nodeFront);
container.addAttribute();
},
/**
* Copy attribute value for node.
* Used for node context menu and shouldn't be called directly.
*/
onCopyAttributeValue: function() {
clipboardHelper.copyString(this.nodeMenuTriggerInfo.value);
},
/**
* Edit attribute for node.
* Used for node context menu and shouldn't be called directly.
*/
onEditAttribute: function() {
const container = this.markup.getContainer(this.selection.nodeFront);
container.editAttribute(this.nodeMenuTriggerInfo.name);
},
/**
* Remove attribute from node.
* Used for node context menu and shouldn't be called directly.
*/
onRemoveAttribute: function() {
const container = this.markup.getContainer(this.selection.nodeFront);
container.removeAttribute(this.nodeMenuTriggerInfo.name);
},
expandNode: function() {
this.markup.expandAll(this.selection.nodeFront);
},
collapseAll: function() {
this.markup.collapseAll(this.selection.nodeFront);
},
/**
* This method is here for the benefit of the node-menu-link-follow menu item
* in the inspector contextual-menu.
*/
onFollowLink: function() {
const type = this.contextMenuTarget.dataset.type;
const link = this.contextMenuTarget.dataset.link;
this.followAttributeLink(type, link);
},
/**
* Given a type and link found in a node's attribute in the markup-view,
* attempt to follow that link (which may result in opening a new tab, the
* style editor or debugger).
*/
followAttributeLink: function(type, link) {
if (!type || !link) {
return;
}
if (type === "uri" || type === "cssresource" || type === "jsresource") {
// Open link in a new tab.
this.inspector.resolveRelativeURL(
link, this.selection.nodeFront).then(url => {
if (type === "uri") {
openContentLink(url);
} else if (type === "cssresource") {
return this.toolbox.viewSourceInStyleEditor(url);
} else if (type === "jsresource") {
return this.toolbox.viewSourceInDebugger(url);
}
return null;
}).catch(console.error);
} else if (type == "idref") {
// Select the node in the same document.
this.walker.document(this.selection.nodeFront).then(doc => {
return this.walker.querySelector(doc, "#" + CSS.escape(link)).then(node => {
if (!node) {
this.emit("idref-attribute-link-failed");
return;
}
this.selection.setNodeFront(node);
});
}).catch(console.error);
}
},
/**
* This method is here for the benefit of the node-menu-link-copy menu item
* in the inspector contextual-menu.
*/
onCopyLink: function() {
const link = this.contextMenuTarget.dataset.link;
this.copyAttributeLink(link);
},
/**
* This method is here for the benefit of copying links.
*/
copyAttributeLink: function(link) {
this.inspector.resolveRelativeURL(link, this.selection.nodeFront).then(url => {
clipboardHelper.copyString(url);
}, console.error);
},
/**
* Returns an object containing the shared handler functions used in the box
* model and grid React components.
@ -2464,18 +1618,6 @@ Inspector.prototype = {
toolbox.highlighter.highlight(nodeFront, options);
},
/**
* Returns a value indicating whether a node can be deleted.
*
* @param {NodeFront} nodeFront
* The node to test for deletion
*/
isDeletable(nodeFront) {
return !(nodeFront.isDocumentElement ||
nodeFront.nodeType == nodeConstants.DOCUMENT_TYPE_NODE ||
nodeFront.isAnonymous);
},
async inspectNodeActor(nodeActor, inspectFromAnnotation) {
const nodeFront = await this.walker.gripToNodeFront({ actor: nodeActor });
if (!nodeFront) {

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 RootContainer = require("devtools/client/inspector/markup/views/root-container");
loader.lazyRequireGetter(this, "MarkupContextMenu", "devtools/client/inspector/markup/markup-context-menu");
loader.lazyRequireGetter(this, "SlottedNodeContainer", "devtools/client/inspector/markup/views/slotted-node-container");
loader.lazyRequireGetter(this, "copyLongString", "devtools/client/inspector/shared/utils", true);
loader.lazyRequireGetter(this, "getLongString", "devtools/client/inspector/shared/utils", true);
loader.lazyRequireGetter(this, "openContentLink", "devtools/client/shared/link", true);
loader.lazyRequireGetter(this, "HTMLTooltip", "devtools/client/shared/widgets/tooltip/HTMLTooltip", true);
loader.lazyRequireGetter(this, "UndoStack", "devtools/client/shared/undo", true);
loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
const INSPECTOR_L10N =
new LocalizationHelper("devtools/client/locales/inspector.properties");
@ -98,6 +103,7 @@ function MarkupView(inspector, frame, controllerWindow) {
this._isImagePreviewTarget = this._isImagePreviewTarget.bind(this);
this._mutationObserver = this._mutationObserver.bind(this);
this._onBlur = this._onBlur.bind(this);
this._onContextMenu = this._onContextMenu.bind(this);
this._onCopy = this._onCopy.bind(this);
this._onCollapseAttributesPrefChange = this._onCollapseAttributesPrefChange.bind(this);
this._onWalkerNodeStatesChanged = this._onWalkerNodeStatesChanged.bind(this);
@ -113,6 +119,7 @@ function MarkupView(inspector, frame, controllerWindow) {
// Listening to various events.
this._elt.addEventListener("blur", this._onBlur, true);
this._elt.addEventListener("click", this._onMouseClick);
this._elt.addEventListener("contextmenu", this._onContextMenu);
this._elt.addEventListener("mousemove", this._onMouseMove);
this._elt.addEventListener("mouseout", this._onMouseOut);
this._frame.addEventListener("focus", this._onFocus);
@ -156,6 +163,14 @@ MarkupView.prototype = {
_selectedContainer: null,
get contextMenu() {
if (!this._contextMenu) {
this._contextMenu = new MarkupContextMenu(this);
}
return this._contextMenu;
},
get eventDetailsTooltip() {
if (!this._eventDetailsTooltip) {
// This tooltip will be attached to the toolbox document.
@ -279,6 +294,10 @@ MarkupView.prototype = {
}
},
_onContextMenu: function(event) {
this.contextMenu.show(event);
},
/**
* Executed on each mouse-move while a node is being dragged in the view.
* Auto-scrolls the view to reveal nodes below the fold to drop the dragged
@ -766,12 +785,73 @@ MarkupView.prototype = {
const selection = this.inspector.selection;
if (selection.isNode()) {
this.inspector.copyOuterHTML();
this.copyOuterHTML();
}
evt.stopPropagation();
evt.preventDefault();
},
/**
* Copy the outerHTML of the selected Node to the clipboard.
*/
copyOuterHTML: function() {
if (!this.inspector.selection.isNode()) {
return;
}
const node = this.inspector.selection.nodeFront;
switch (node.nodeType) {
case nodeConstants.ELEMENT_NODE :
copyLongString(this.walker.outerHTML(node));
break;
case nodeConstants.COMMENT_NODE :
getLongString(node.getNodeValue()).then(comment => {
clipboardHelper.copyString("<!--" + 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.
*/
@ -820,8 +900,7 @@ MarkupView.prototype = {
break;
}
case "markupView.scrollInto.key": {
const selection = this._selectedContainer.node;
this.inspector.scrollNodeIntoView(selection);
this.scrollNodeIntoView();
break;
}
// Generic keys
@ -960,6 +1039,18 @@ MarkupView.prototype = {
}
},
/**
* Returns a value indicating whether a node can be deleted.
*
* @param {NodeFront} nodeFront
* The node to test for deletion
*/
isDeletable(nodeFront) {
return !(nodeFront.isDocumentElement ||
nodeFront.nodeType == nodeConstants.DOCUMENT_TYPE_NODE ||
nodeFront.isAnonymous);
},
/**
* Delete a node from the DOM.
* This is an undoable action.
@ -970,7 +1061,7 @@ MarkupView.prototype = {
* If set to true, focus the previous sibling, otherwise the next one.
*/
deleteNode: function(node, moveBackward) {
if (!this.inspector.isDeletable(node)) {
if (!this.isDeletable(node)) {
return;
}
@ -1017,6 +1108,17 @@ MarkupView.prototype = {
}).catch(console.error);
},
/**
* Scroll the node into view.
*/
scrollNodeIntoView() {
if (!this.inspector.selection.isNode()) {
return;
}
this.inspector.selection.nodeFront.scrollIntoView();
},
/**
* If an editable item is focused, select its container.
*/
@ -1952,6 +2054,7 @@ MarkupView.prototype = {
this._elt.removeEventListener("blur", this._onBlur, true);
this._elt.removeEventListener("click", this._onMouseClick);
this._elt.removeEventListener("contextmenu", this._onContextMenu);
this._elt.removeEventListener("mousemove", this._onMouseMove);
this._elt.removeEventListener("mouseout", this._onMouseOut);
this._frame.removeEventListener("focus", this._onFocus);

View file

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

View file

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

View file

@ -99,7 +99,7 @@ async function followLinkWaitForNewNode(linkEl, isMetaClick, inspector) {
}
async function followLinkNoNewNode(linkEl, isMetaClick, inspector) {
const onFailed = inspector.once("idref-attribute-link-failed");
const onFailed = inspector.markup.once("idref-attribute-link-failed");
performMouseDown(linkEl, isMetaClick);
await onFailed;

View file

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

View file

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

View file

@ -11,6 +11,7 @@ const promise = require("promise");
loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
loader.lazyRequireGetter(this, "getCSSLexer", "devtools/shared/css/lexer", true);
loader.lazyRequireGetter(this, "parseDeclarations", "devtools/shared/css/parsing-utils", true);
loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
const HTML_NS = "http://www.w3.org/1999/xhtml";
@ -82,6 +83,21 @@ function blurOnMultipleProperties(cssProperties) {
};
}
/**
* Copy the content of a longString (via a promise resolving a
* LongStringActor) to the clipboard.
*
* @param {Promise} longStringActorPromise
* promise expected to resolve a LongStringActor instance
* @return {Promise} promise resolving (with no argument) when the
* string is sent to the clipboard
*/
function copyLongString(longStringActorPromise) {
return getLongString(longStringActorPromise).then(string => {
clipboardHelper.copyString(string);
}).catch(console.error);
}
/**
* Create a child element with a set of attributes.
*
@ -109,6 +125,22 @@ function createChild(parent, tagName, attributes = {}) {
return elt;
}
/**
* Retrieve the content of a longString (via a promise resolving a LongStringActor).
*
* @param {Promise} longStringActorPromise
* promise expected to resolve a LongStringActor instance
* @return {Promise} promise resolving with the retrieved string as argument
*/
function getLongString(longStringActorPromise) {
return longStringActorPromise.then(longStringActor => {
return longStringActor.string().then(string => {
longStringActor.release().catch(console.error);
return string;
});
}).catch(console.error);
}
/**
* Returns a selector of the Element Rep from the grip. This is based on the
* getElements() function in our devtools-reps component for a ElementNode.
@ -196,7 +228,9 @@ function translateNodeFrontToGrip(nodeFront) {
exports.advanceValidate = advanceValidate;
exports.appendText = appendText;
exports.blurOnMultipleProperties = blurOnMultipleProperties;
exports.copyLongString = copyLongString;
exports.createChild = createChild;
exports.getLongString = getLongString;
exports.getSelectorFromGrip = getSelectorFromGrip;
exports.promiseWarn = promiseWarn;
exports.translateNodeFrontToGrip = translateNodeFrontToGrip;

View file

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

View file

@ -82,7 +82,7 @@ add_task(async function() {
async function checkTextBox(textBox, toolbox) {
let textboxContextMenu = toolbox.doc.getElementById("toolbox-menu");
ok(!textboxContextMenu, "The menu is closed");
ok(!textboxContextMenu, "The menu is closed");
info("Simulating context click on the textbox and expecting the menu to open");
const onContextMenu = toolbox.once("menu-open");

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

@ -29,27 +29,8 @@ interface nsIProxiedProtocolHandler : nsIProtocolHandler
* a ws:// uri.
* @param aLoadInfo used to evaluate who initated the resource request.
*/
nsIChannel newProxiedChannel2(in nsIURI uri, in nsIProxyInfo proxyInfo,
in unsigned long proxyResolveFlags,
in nsIURI proxyURI,
in nsILoadInfo aLoadInfo);
/** Create a new channel with the given proxyInfo
*
* @param uri the channel uri
* @param proxyInfo any proxy information that has already been determined,
* or null if channel should later determine the proxy on its own using
* proxyResolveFlags/proxyURI
* @param proxyResolveFlags used if the proxy is later determined
* from nsIProtocolProxyService::asyncResolve
* @param proxyURI used if the proxy is later determined from
* nsIProtocolProxyService::asyncResolve with this as the proxyURI name.
* Generally this is the same as uri (or null which has the same
* effect), except in the case of websockets which wants to bootstrap
* to an http:// channel but make its proxy determination based on
* a ws:// uri.
*/
nsIChannel newProxiedChannel(in nsIURI uri, in nsIProxyInfo proxyInfo,
in unsigned long proxyResolveFlags,
in nsIURI proxyURI);
in nsIURI proxyURI,
in nsILoadInfo aLoadInfo);
};

View file

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

View file

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

View file

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

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

View file

@ -60,7 +60,7 @@ nsresult nsViewSourceChannel::Init(nsIURI *uri) {
return NS_ERROR_INVALID_ARG;
}
// This function is called from within nsViewSourceHandler::NewChannel2
// This function is called from within nsViewSourceHandler::NewChannel
// and sets the right loadInfo right after returning from this function.
// Until then we follow the principal of least privilege and use
// nullPrincipal as the loadingPrincipal and the least permissive
@ -68,7 +68,7 @@ nsresult nsViewSourceChannel::Init(nsIURI *uri) {
nsCOMPtr<nsIPrincipal> nullPrincipal =
mozilla::NullPrincipal::CreateWithoutOriginAttributes();
rv = pService->NewChannel2(
rv = pService->NewChannel(
path,
nullptr, // aOriginCharset
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
// allow setting proxy uri/flags
rv = ioService->NewChannelFromURIWithProxyFlags2(
rv = ioService->NewChannelFromURIWithProxyFlags(
localURI, mURI,
nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
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
// the channel the utility method creates.
let ios = Services.io
let iosChannel = ios.newChannel2(TEST_SPEC,
null,
null,
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER);
let iosChannel = ios.newChannel(TEST_SPEC,
null,
null,
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER);
let NetUtilChannel = NetUtil.newChannel({
uri: TEST_SPEC,
loadUsingSystemPrincipal: true
@ -537,12 +537,12 @@ function test_newChannel_with_nsIURI()
// Check that we get the same URI back from channel the IO service creates and
// the channel the utility method creates.
let uri = NetUtil.newURI(TEST_SPEC);
let iosChannel = Services.io.newChannelFromURI2(uri,
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER);
let iosChannel = Services.io.newChannelFromURI(uri,
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER);
let NetUtilChannel = NetUtil.newChannel({
uri: uri,
loadUsingSystemPrincipal: true
@ -556,12 +556,12 @@ function test_newChannel_with_options()
{
let uri = "data:text/plain,";
let iosChannel = Services.io.newChannelFromURI2(NetUtil.newURI(uri),
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER);
let iosChannel = Services.io.newChannelFromURI(NetUtil.newURI(uri),
null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER);
function checkEqualToIOSChannel(channel) {
Assert.ok(iosChannel.URI.equals(channel.URI));

View file

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

View file

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

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 \
p7verify \
pk12util \
pk11importtest \
pk11ectest \
pk11gcmtest \
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."

View file

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

View file

@ -33,6 +33,7 @@ struct ScopedMaybeDelete {
SCOPED(SECAlgorithmID);
SCOPED(SECItem);
SCOPED(PK11URI);
SCOPED(PLArenaPool);
#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");
}
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 uint16_t NormalizeTlsVersion(uint16_t version) {

View file

@ -10,7 +10,23 @@
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
if (NSS_NoDB_Init(nullptr) != SECSuccess) {
const char *workdir = "";
uint32_t flags = NSS_INIT_READONLY;
for (int i = 0; i < argc; i++) {
if (!strcmp(argv[i], "-d")) {
if (i + 1 >= argc) {
PR_fprintf(PR_STDERR, "Usage: %s [-d <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;
}
if (NSS_SetDomesticPolicy() != SECSuccess) {

View file

@ -13,6 +13,7 @@ CPPSRCS = \
pk11_ecdsa_unittest.cc \
pk11_encrypt_derive_unittest.cc \
pk11_export_unittest.cc \
pk11_import_unittest.cc \
pk11_pbkdf2_unittest.cc \
pk11_prf_unittest.cc \
pk11_prng_unittest.cc \
@ -33,4 +34,3 @@ EXTRA_LIBS = $(DIST)/lib/$(LIB_PREFIX)gtest.$(LIB_SUFFIX) \
$(DIST)/lib/$(LIB_PREFIX)cpputil.$(LIB_SUFFIX) \
$(DIST)/lib/$(LIB_PREFIX)gtestutil.$(LIB_SUFFIX) \
$(NULL)

View file

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

View file

@ -297,38 +297,6 @@ SSLKEAType SSLInt_GetKEAType(SSLNamedGroup group) {
return groupDef->keaType;
}
SECStatus SSLInt_SetCipherSpecChangeFunc(PRFileDesc *fd,
sslCipherSpecChangedFunc func,
void *arg) {
sslSocket *ss;
ss = ssl_FindSocket(fd);
if (!ss) {
return SECFailure;
}
ss->ssl3.changedCipherSpecFunc = func;
ss->ssl3.changedCipherSpecArg = arg;
return SECSuccess;
}
PK11SymKey *SSLInt_CipherSpecToKey(const ssl3CipherSpec *spec) {
return spec->keyMaterial.key;
}
SSLCipherAlgorithm SSLInt_CipherSpecToAlgorithm(const ssl3CipherSpec *spec) {
return spec->cipherDef->calg;
}
const PRUint8 *SSLInt_CipherSpecToIv(const ssl3CipherSpec *spec) {
return spec->keyMaterial.iv;
}
PRUint16 SSLInt_CipherSpecToEpoch(const ssl3CipherSpec *spec) {
return spec->epoch;
}
void SSLInt_SetTicketLifetime(uint32_t lifetime) {
ssl_ticket_lifetime = lifetime;
}
@ -360,16 +328,14 @@ void SSLInt_RolloverAntiReplay(void) {
tls13_AntiReplayRollover(ssl_TimeUsec());
}
SECStatus SSLInt_GetEpochs(PRFileDesc *fd, PRUint16 *readEpoch,
PRUint16 *writeEpoch) {
SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending) {
sslSocket *ss = ssl_FindSocket(fd);
if (!ss || !readEpoch || !writeEpoch) {
if (!ss) {
return SECFailure;
}
ssl_GetSpecReadLock(ss);
*readEpoch = ss->ssl3.crSpec->epoch;
*writeEpoch = ss->ssl3.cwSpec->epoch;
ssl_ReleaseSpecReadLock(ss);
ssl_GetSSL3HandshakeLock(ss);
*pending = ss->ssl3.hs.msg_body.len > 0;
ssl_ReleaseSSL3HandshakeLock(ss);
return SECSuccess;
}

View file

@ -39,16 +39,7 @@ SECStatus SSLInt_AdvanceWriteSeqNum(PRFileDesc *fd, PRUint64 to);
SECStatus SSLInt_AdvanceReadSeqNum(PRFileDesc *fd, PRUint64 to);
SECStatus SSLInt_AdvanceWriteSeqByAWindow(PRFileDesc *fd, PRInt32 extra);
SSLKEAType SSLInt_GetKEAType(SSLNamedGroup group);
SECStatus SSLInt_GetEpochs(PRFileDesc *fd, PRUint16 *readEpoch,
PRUint16 *writeEpoch);
SECStatus SSLInt_SetCipherSpecChangeFunc(PRFileDesc *fd,
sslCipherSpecChangedFunc func,
void *arg);
PRUint16 SSLInt_CipherSpecToEpoch(const ssl3CipherSpec *spec);
PK11SymKey *SSLInt_CipherSpecToKey(const ssl3CipherSpec *spec);
SSLCipherAlgorithm SSLInt_CipherSpecToAlgorithm(const ssl3CipherSpec *spec);
const PRUint8 *SSLInt_CipherSpecToIv(const ssl3CipherSpec *spec);
SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending);
void SSLInt_SetTicketLifetime(uint32_t lifetime);
SECStatus SSLInt_SetSocketMaxEarlyDataSize(PRFileDesc *fd, uint32_t size);
void SSLInt_RolloverAntiReplay(void);

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

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() {
EXPECT_TRUE(EnsureTlsSetup());
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;
void EnableSrtp();
void CheckSrtp() const;
void CheckEpochs(uint16_t expected_read, uint16_t expected_write) const;
void CheckErrorCode(int32_t expected) const;
void WaitForErrorCode(int32_t expected, uint32_t delay) const;
// Send data on the socket, encrypting it.

View file

@ -167,18 +167,8 @@ void TlsConnectTestBase::CheckShares(
void TlsConnectTestBase::CheckEpochs(uint16_t client_epoch,
uint16_t server_epoch) const {
uint16_t read_epoch = 0;
uint16_t write_epoch = 0;
EXPECT_EQ(SECSuccess,
SSLInt_GetEpochs(client_->ssl_fd(), &read_epoch, &write_epoch));
EXPECT_EQ(server_epoch, read_epoch) << "client read epoch";
EXPECT_EQ(client_epoch, write_epoch) << "client write epoch";
EXPECT_EQ(SECSuccess,
SSLInt_GetEpochs(server_->ssl_fd(), &read_epoch, &write_epoch));
EXPECT_EQ(client_epoch, read_epoch) << "server read epoch";
EXPECT_EQ(server_epoch, write_epoch) << "server write epoch";
client_->CheckEpochs(server_epoch, client_epoch);
server_->CheckEpochs(client_epoch, server_epoch);
}
void TlsConnectTestBase::ClearStats() {

View file

@ -45,40 +45,65 @@ void TlsVersioned::WriteStream(std::ostream& stream) const {
}
}
void TlsRecordFilter::EnableDecryption() {
SSLInt_SetCipherSpecChangeFunc(agent()->ssl_fd(), CipherSpecChanged,
(void*)this);
TlsRecordFilter::TlsRecordFilter(const std::shared_ptr<TlsAgent>& a)
: agent_(a) {
cipher_specs_.emplace_back(a->variant() == ssl_variant_datagram, 0);
}
void TlsRecordFilter::CipherSpecChanged(void* arg, PRBool sending,
ssl3CipherSpec* newSpec) {
TlsRecordFilter* self = static_cast<TlsRecordFilter*>(arg);
PRBool isServer = self->agent()->role() == TlsAgent::SERVER;
void TlsRecordFilter::EnableDecryption() {
EXPECT_EQ(SECSuccess,
SSL_SecretCallback(agent()->ssl_fd(), SecretCallback, this));
decrypting_ = true;
}
void TlsRecordFilter::SecretCallback(PRFileDesc* fd, PRUint16 epoch,
SSLSecretDirection dir, PK11SymKey* secret,
void* arg) {
TlsRecordFilter* self = static_cast<TlsRecordFilter*>(arg);
if (g_ssl_gtest_verbose) {
std::cerr << (isServer ? "server" : "client") << ": "
<< (sending ? "send" : "receive")
<< " cipher spec changed: " << newSpec->epoch << " ("
<< newSpec->phase << ")" << std::endl;
std::cerr << self->agent()->role_str() << ": " << dir
<< " secret changed for epoch " << epoch << std::endl;
}
if (!sending) {
if (dir == ssl_secret_read) {
return;
}
uint64_t seq_no;
if (self->agent()->variant() == ssl_variant_datagram) {
seq_no = static_cast<uint64_t>(SSLInt_CipherSpecToEpoch(newSpec)) << 48;
} else {
seq_no = 0;
for (auto& spec : self->cipher_specs_) {
ASSERT_NE(spec.epoch(), epoch) << "duplicate spec for epoch " << epoch;
}
self->in_sequence_number_ = seq_no;
self->out_sequence_number_ = seq_no;
self->dropped_record_ = false;
self->cipher_spec_.reset(new TlsCipherSpec());
bool ret = self->cipher_spec_->Init(
SSLInt_CipherSpecToEpoch(newSpec), SSLInt_CipherSpecToAlgorithm(newSpec),
SSLInt_CipherSpecToKey(newSpec), SSLInt_CipherSpecToIv(newSpec));
EXPECT_EQ(true, ret);
SSLPreliminaryChannelInfo preinfo;
EXPECT_EQ(SECSuccess,
SSL_GetPreliminaryChannelInfo(self->agent()->ssl_fd(), &preinfo,
sizeof(preinfo)));
EXPECT_EQ(sizeof(preinfo), preinfo.length);
// Check the version.
if (preinfo.valuesSet & ssl_preinfo_version) {
EXPECT_EQ(SSL_LIBRARY_VERSION_TLS_1_3, preinfo.protocolVersion);
} else {
EXPECT_EQ(1U, epoch);
}
uint16_t suite;
if (epoch == 1) {
// 0-RTT
EXPECT_TRUE(preinfo.valuesSet & ssl_preinfo_0rtt_cipher_suite);
suite = preinfo.zeroRttCipherSuite;
} else {
EXPECT_TRUE(preinfo.valuesSet & ssl_preinfo_cipher_suite);
suite = preinfo.cipherSuite;
}
SSLCipherSuiteInfo cipherinfo;
EXPECT_EQ(SECSuccess,
SSL_GetCipherSuiteInfo(suite, &cipherinfo, sizeof(cipherinfo)));
EXPECT_EQ(sizeof(cipherinfo), cipherinfo.length);
bool is_dtls = self->agent()->variant() == ssl_variant_datagram;
self->cipher_specs_.emplace_back(is_dtls, epoch);
EXPECT_TRUE(self->cipher_specs_.back().SetKeys(&cipherinfo, secret));
}
bool TlsRecordFilter::is_dtls13() const {
@ -95,6 +120,23 @@ bool TlsRecordFilter::is_dtls13() const {
info.canSendEarlyData;
}
// Gets the cipher spec that matches the specified epoch.
TlsCipherSpec& TlsRecordFilter::spec(uint16_t write_epoch) {
for (auto& sp : cipher_specs_) {
if (sp.epoch() == write_epoch) {
return sp;
}
}
// If we aren't decrypting, provide a cipher spec that does nothing other than
// count sequence numbers.
EXPECT_FALSE(decrypting_) << "No spec available for epoch " << write_epoch;
;
bool is_dtls = agent()->variant() == ssl_variant_datagram;
cipher_specs_.emplace_back(is_dtls, write_epoch);
return cipher_specs_.back();
}
PacketFilter::Action TlsRecordFilter::Filter(const DataBuffer& input,
DataBuffer* output) {
// Disable during shutdown.
@ -108,34 +150,28 @@ PacketFilter::Action TlsRecordFilter::Filter(const DataBuffer& input,
output->Allocate(input.len());
TlsParser parser(input);
// This uses the current write spec for the purposes of parsing the epoch and
// sequence number from the header. This might be wrong because we can
// receive records from older specs, but guessing is good enough:
// - In DTLS, parsing the sequence number corrects any errors.
// - In TLS, we don't use the sequence number unless decrypting, where we use
// trial decryption to get the right epoch.
uint16_t write_epoch = 0;
SECStatus rv = SSL_GetCurrentEpoch(agent()->ssl_fd(), nullptr, &write_epoch);
if (rv != SECSuccess) {
ADD_FAILURE() << "unable to read epoch";
return KEEP;
}
uint64_t guess_seqno = static_cast<uint64_t>(write_epoch) << 48;
while (parser.remaining()) {
TlsRecordHeader header;
DataBuffer record;
if (!header.Parse(is_dtls13(), in_sequence_number_, &parser, &record)) {
if (!header.Parse(is_dtls13(), guess_seqno, &parser, &record)) {
ADD_FAILURE() << "not a valid record";
return KEEP;
}
// Track the sequence number, which is necessary for stream mode when
// decrypting and for TLS 1.3 datagram to recover the sequence number.
//
// We reset the counter when the cipher spec changes, but that notification
// appears before a record is sent. If multiple records are sent with
// different cipher specs, this would fail. This filters out cleartext
// records, so we don't get confused by handshake messages that are sent at
// the same time as encrypted records. Sequence numbers are therefore
// likely to be incorrect for cleartext records.
//
// This isn't perfectly robust: if there is a change from an active cipher
// spec to another active cipher spec (KeyUpdate for instance) AND writes
// are consolidated across that change, this code could use the wrong
// sequence numbers when re-encrypting records with the old keys.
if (header.content_type() == ssl_ct_application_data) {
in_sequence_number_ =
(std::max)(in_sequence_number_, header.sequence_number() + 1);
}
if (FilterRecord(header, record, &offset, output) != KEEP) {
changed = true;
} else {
@ -159,14 +195,16 @@ PacketFilter::Action TlsRecordFilter::FilterRecord(
DataBuffer filtered;
uint8_t inner_content_type;
DataBuffer plaintext;
uint16_t protection_epoch = 0;
if (!Unprotect(header, record, &inner_content_type, &plaintext)) {
if (g_ssl_gtest_verbose) {
std::cerr << "unprotect failed: " << header << ":" << record << std::endl;
}
if (!Unprotect(header, record, &protection_epoch, &inner_content_type,
&plaintext)) {
std::cerr << agent()->role_str() << ": unprotect failed: " << header << ":"
<< record << std::endl;
return KEEP;
}
auto& protection_spec = spec(protection_epoch);
TlsRecordHeader real_header(header.variant(), header.version(),
inner_content_type, header.sequence_number());
@ -174,7 +212,9 @@ PacketFilter::Action TlsRecordFilter::FilterRecord(
// In stream mode, even if something doesn't change we need to re-encrypt if
// previous packets were dropped.
if (action == KEEP) {
if (header.is_dtls() || !dropped_record_) {
if (header.is_dtls() || !protection_spec.record_dropped()) {
// Count every outgoing packet.
protection_spec.RecordProtected();
return KEEP;
}
filtered = plaintext;
@ -182,7 +222,7 @@ PacketFilter::Action TlsRecordFilter::FilterRecord(
if (action == DROP) {
std::cerr << "record drop: " << header << ":" << record << std::endl;
dropped_record_ = true;
protection_spec.RecordDropped();
return DROP;
}
@ -192,19 +232,18 @@ PacketFilter::Action TlsRecordFilter::FilterRecord(
std::cerr << "record new: " << filtered << std::endl;
}
uint64_t seq_num;
if (header.is_dtls() || !cipher_spec_ ||
header.content_type() != ssl_ct_application_data) {
seq_num = header.sequence_number();
} else {
seq_num = out_sequence_number_++;
uint64_t seq_num = protection_spec.next_out_seqno();
if (!decrypting_ && header.is_dtls()) {
// Copy over the epoch, which isn't tracked when not decrypting.
seq_num |= header.sequence_number() & (0xffffULL << 48);
}
TlsRecordHeader out_header(header.variant(), header.version(),
header.content_type(), seq_num);
DataBuffer ciphertext;
bool rv = Protect(out_header, inner_content_type, filtered, &ciphertext);
EXPECT_TRUE(rv);
bool rv = Protect(protection_spec, out_header, inner_content_type, filtered,
&ciphertext);
if (!rv) {
return KEEP;
}
@ -227,15 +266,20 @@ uint64_t TlsRecordHeader::RecoverSequenceNumber(uint64_t expected,
uint32_t partial,
size_t partial_bits) {
EXPECT_GE(32U, partial_bits);
uint64_t mask = (1 << partial_bits) - 1;
uint64_t mask = (1ULL << partial_bits) - 1;
// First we determine the highest possible value. This is half the
// expressible range above the expected value.
uint64_t cap = expected + (1ULL << (partial_bits - 1));
// expressible range above the expected value, less 1.
//
// We subtract the extra 1 from the cap so that when given a choice between
// the equidistant expected+N and expected-N we want to chose the lower. With
// 0-RTT, we sometimes have to recover an epoch of 1 when we expect an epoch
// of 3 and with 2 partial bits, the alternative result of 5 is wrong.
uint64_t cap = expected + (1ULL << (partial_bits - 1)) - 1;
// Add the partial piece in. e.g., xxxx789a and 1234 becomes xxxx1234.
uint64_t seq_no = (cap & ~mask) | partial;
// If the partial value is higher than the same partial piece from the cap,
// then the real value has to be lower. e.g., xxxx1234 can't become xxxx5678.
if (partial > (cap & mask)) {
if (partial > (cap & mask) && (seq_no >= (1ULL << partial_bits))) {
seq_no -= 1ULL << partial_bits;
}
return seq_no;
@ -375,16 +419,41 @@ size_t TlsRecordHeader::Write(DataBuffer* buffer, size_t offset,
bool TlsRecordFilter::Unprotect(const TlsRecordHeader& header,
const DataBuffer& ciphertext,
uint16_t* protection_epoch,
uint8_t* inner_content_type,
DataBuffer* plaintext) {
if (!cipher_spec_ || header.content_type() != ssl_ct_application_data) {
if (!decrypting_ || header.content_type() != ssl_ct_application_data) {
// Maintain the epoch and sequence number for plaintext records.
uint16_t ep = 0;
if (agent()->variant() == ssl_variant_datagram) {
ep = static_cast<uint16_t>(header.sequence_number() >> 48);
}
spec(ep).RecordUnprotected(header.sequence_number());
*protection_epoch = ep;
*inner_content_type = header.content_type();
*plaintext = ciphertext;
return true;
}
if (!cipher_spec_->Unprotect(header, ciphertext, plaintext)) {
return false;
uint16_t ep = 0;
if (agent()->variant() == ssl_variant_datagram) {
ep = static_cast<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();
@ -396,33 +465,45 @@ bool TlsRecordFilter::Unprotect(const TlsRecordHeader& header,
return false;
}
*protection_epoch = ep;
*inner_content_type = plaintext->data()[len - 1];
plaintext->Truncate(len - 1);
if (g_ssl_gtest_verbose) {
std::cerr << "unprotect: " << std::hex << header.sequence_number()
<< std::dec << " type=" << static_cast<int>(*inner_content_type)
std::cerr << agent()->role_str() << ": unprotect: epoch=" << ep
<< " seq=" << std::hex << header.sequence_number() << std::dec
<< " " << *plaintext << std::endl;
}
return true;
}
bool TlsRecordFilter::Protect(const TlsRecordHeader& header,
bool TlsRecordFilter::Protect(TlsCipherSpec& protection_spec,
const TlsRecordHeader& header,
uint8_t inner_content_type,
const DataBuffer& plaintext,
DataBuffer* ciphertext, size_t padding) {
if (!cipher_spec_ || header.content_type() != ssl_ct_application_data) {
if (!protection_spec.is_protected()) {
// Not protected, just keep the sequence numbers updated.
protection_spec.RecordProtected();
*ciphertext = plaintext;
return true;
}
if (g_ssl_gtest_verbose) {
std::cerr << "protect: " << header.sequence_number() << std::endl;
}
DataBuffer padded;
padded.Allocate(plaintext.len() + 1 + padding);
size_t offset = padded.Write(0, plaintext.data(), plaintext.len());
padded.Write(offset, inner_content_type, 1);
return cipher_spec_->Protect(header, padded, ciphertext);
bool ok = protection_spec.Protect(header, padded, ciphertext);
if (!ok) {
ADD_FAILURE() << "protect fail";
} else if (g_ssl_gtest_verbose) {
std::cerr << agent()->role_str()
<< ": protect: epoch=" << protection_spec.epoch()
<< " seq=" << std::hex << header.sequence_number() << std::dec
<< " " << *ciphertext << std::endl;
}
return ok;
}
bool IsHelloRetry(const DataBuffer& body) {

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

View file

@ -7,6 +7,9 @@
#include "tls_protect.h"
#include "tls_filter.h"
// Do this to avoid having to re-implement HKDF.
#include "tls13hkdf.h"
namespace nss_test {
AeadCipher::~AeadCipher() {
@ -15,32 +18,37 @@ AeadCipher::~AeadCipher() {
}
}
bool AeadCipher::Init(PK11SymKey *key, const uint8_t *iv) {
key_ = PK11_ReferenceSymKey(key);
bool AeadCipher::Init(PK11SymKey* key, const uint8_t* iv) {
key_ = key;
if (!key_) return false;
memcpy(iv_, iv, sizeof(iv_));
if (g_ssl_gtest_verbose) {
EXPECT_EQ(SECSuccess, PK11_ExtractKeyValue(key_));
SECItem* raw_key = PK11_GetKeyData(key_);
std::cerr << "key: " << DataBuffer(raw_key->data, raw_key->len)
<< std::endl;
std::cerr << "iv: " << DataBuffer(iv_, 12) << std::endl;
}
return true;
}
void AeadCipher::FormatNonce(uint64_t seq, uint8_t *nonce) {
void AeadCipher::FormatNonce(uint64_t seq, uint8_t* nonce) {
memcpy(nonce, iv_, 12);
for (size_t i = 0; i < 8; ++i) {
nonce[12 - (i + 1)] ^= seq & 0xff;
seq >>= 8;
}
DataBuffer d(nonce, 12);
}
bool AeadCipher::AeadInner(bool decrypt, void *params, size_t param_length,
const uint8_t *in, size_t inlen, uint8_t *out,
size_t *outlen, size_t maxlen) {
bool AeadCipher::AeadInner(bool decrypt, void* params, size_t param_length,
const uint8_t* in, size_t inlen, uint8_t* out,
size_t* outlen, size_t maxlen) {
SECStatus rv;
unsigned int uoutlen = 0;
SECItem param = {
siBuffer, static_cast<unsigned char *>(params),
siBuffer, static_cast<unsigned char*>(params),
static_cast<unsigned int>(param_length),
};
@ -54,28 +62,28 @@ bool AeadCipher::AeadInner(bool decrypt, void *params, size_t param_length,
return rv == SECSuccess;
}
bool AeadCipherAesGcm::Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len,
uint64_t seq, const uint8_t *in, size_t inlen,
uint8_t *out, size_t *outlen, size_t maxlen) {
bool AeadCipherAesGcm::Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len,
uint64_t seq, const uint8_t* in, size_t inlen,
uint8_t* out, size_t* outlen, size_t maxlen) {
CK_GCM_PARAMS aeadParams;
unsigned char nonce[12];
memset(&aeadParams, 0, sizeof(aeadParams));
aeadParams.pIv = nonce;
aeadParams.ulIvLen = sizeof(nonce);
aeadParams.pAAD = const_cast<uint8_t *>(hdr);
aeadParams.pAAD = const_cast<uint8_t*>(hdr);
aeadParams.ulAADLen = hdr_len;
aeadParams.ulTagBits = 128;
FormatNonce(seq, nonce);
return AeadInner(decrypt, (unsigned char *)&aeadParams, sizeof(aeadParams),
in, inlen, out, outlen, maxlen);
return AeadInner(decrypt, (unsigned char*)&aeadParams, sizeof(aeadParams), in,
inlen, out, outlen, maxlen);
}
bool AeadCipherChacha20Poly1305::Aead(bool decrypt, const uint8_t *hdr,
bool AeadCipherChacha20Poly1305::Aead(bool decrypt, const uint8_t* hdr,
size_t hdr_len, uint64_t seq,
const uint8_t *in, size_t inlen,
uint8_t *out, size_t *outlen,
const uint8_t* in, size_t inlen,
uint8_t* out, size_t* outlen,
size_t maxlen) {
CK_NSS_AEAD_PARAMS aeadParams;
unsigned char nonce[12];
@ -83,67 +91,126 @@ bool AeadCipherChacha20Poly1305::Aead(bool decrypt, const uint8_t *hdr,
memset(&aeadParams, 0, sizeof(aeadParams));
aeadParams.pNonce = nonce;
aeadParams.ulNonceLen = sizeof(nonce);
aeadParams.pAAD = const_cast<uint8_t *>(hdr);
aeadParams.pAAD = const_cast<uint8_t*>(hdr);
aeadParams.ulAADLen = hdr_len;
aeadParams.ulTagLen = 16;
FormatNonce(seq, nonce);
return AeadInner(decrypt, (unsigned char *)&aeadParams, sizeof(aeadParams),
in, inlen, out, outlen, maxlen);
return AeadInner(decrypt, (unsigned char*)&aeadParams, sizeof(aeadParams), in,
inlen, out, outlen, maxlen);
}
bool TlsCipherSpec::Init(uint16_t epoc, SSLCipherAlgorithm cipher,
PK11SymKey *key, const uint8_t *iv) {
epoch_ = epoc;
switch (cipher) {
static uint64_t FirstSeqno(bool dtls, uint16_t epoc) {
if (dtls) {
return static_cast<uint64_t>(epoc) << 48;
}
return 0;
}
TlsCipherSpec::TlsCipherSpec(bool dtls, uint16_t epoc)
: dtls_(dtls),
epoch_(epoc),
in_seqno_(FirstSeqno(dtls, epoc)),
out_seqno_(FirstSeqno(dtls, epoc)) {}
bool TlsCipherSpec::SetKeys(SSLCipherSuiteInfo* cipherinfo,
PK11SymKey* secret) {
CK_MECHANISM_TYPE mech;
switch (cipherinfo->symCipher) {
case ssl_calg_aes_gcm:
aead_.reset(new AeadCipherAesGcm());
mech = CKM_AES_GCM;
break;
case ssl_calg_chacha20:
aead_.reset(new AeadCipherChacha20Poly1305());
mech = CKM_NSS_CHACHA20_POLY1305;
break;
default:
return false;
}
PK11SymKey* key;
const std::string kPurposeKey = "key";
SECStatus rv = tls13_HkdfExpandLabel(
secret, cipherinfo->kdfHash, NULL, 0, kPurposeKey.c_str(),
kPurposeKey.length(), mech, cipherinfo->symKeyBits / 8, &key);
if (rv != SECSuccess) {
ADD_FAILURE() << "unable to derive key for epoch " << epoch_;
return false;
}
// No constant for IV length, but everything we know of uses 12.
uint8_t iv[12];
const std::string kPurposeIv = "iv";
rv = tls13_HkdfExpandLabelRaw(secret, cipherinfo->kdfHash, NULL, 0,
kPurposeIv.c_str(), kPurposeIv.length(), iv,
sizeof(iv));
if (rv != SECSuccess) {
ADD_FAILURE() << "unable to derive IV for epoch " << epoch_;
return false;
}
return aead_->Init(key, iv);
}
bool TlsCipherSpec::Unprotect(const TlsRecordHeader &header,
const DataBuffer &ciphertext,
DataBuffer *plaintext) {
bool TlsCipherSpec::Unprotect(const TlsRecordHeader& header,
const DataBuffer& ciphertext,
DataBuffer* plaintext) {
if (aead_ == nullptr) {
return false;
}
// Make space.
plaintext->Allocate(ciphertext.len());
auto header_bytes = header.header();
size_t len;
bool ret =
aead_->Aead(true, header_bytes.data(), header_bytes.len(),
header.sequence_number(), ciphertext.data(), ciphertext.len(),
plaintext->data(), &len, plaintext->len());
if (!ret) return false;
uint64_t seqno;
if (dtls_) {
seqno = header.sequence_number();
} else {
seqno = in_seqno_;
}
bool ret = aead_->Aead(true, header_bytes.data(), header_bytes.len(), seqno,
ciphertext.data(), ciphertext.len(), plaintext->data(),
&len, plaintext->len());
if (!ret) {
return false;
}
RecordUnprotected(seqno);
plaintext->Truncate(len);
return true;
}
bool TlsCipherSpec::Protect(const TlsRecordHeader &header,
const DataBuffer &plaintext,
DataBuffer *ciphertext) {
bool TlsCipherSpec::Protect(const TlsRecordHeader& header,
const DataBuffer& plaintext,
DataBuffer* ciphertext) {
if (aead_ == nullptr) {
return false;
}
// Make a padded buffer.
ciphertext->Allocate(plaintext.len() +
32); // Room for any plausible auth tag
size_t len;
DataBuffer header_bytes;
(void)header.WriteHeader(&header_bytes, 0, plaintext.len() + 16);
bool ret =
aead_->Aead(false, header_bytes.data(), header_bytes.len(),
header.sequence_number(), plaintext.data(), plaintext.len(),
ciphertext->data(), &len, ciphertext->len());
if (!ret) return false;
uint64_t seqno;
if (dtls_) {
seqno = header.sequence_number();
} else {
seqno = out_seqno_;
}
bool ret = aead_->Aead(false, header_bytes.data(), header_bytes.len(), seqno,
plaintext.data(), plaintext.len(), ciphertext->data(),
&len, ciphertext->len());
if (!ret) {
return false;
}
RecordProtected();
ciphertext->Truncate(len);
return true;

View file

@ -22,19 +22,19 @@ class AeadCipher {
AeadCipher(CK_MECHANISM_TYPE mech) : mech_(mech), key_(nullptr) {}
virtual ~AeadCipher();
bool Init(PK11SymKey *key, const uint8_t *iv);
virtual bool Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len,
uint64_t seq, const uint8_t *in, size_t inlen, uint8_t *out,
size_t *outlen, size_t maxlen) = 0;
bool Init(PK11SymKey* key, const uint8_t* iv);
virtual bool Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len,
uint64_t seq, const uint8_t* in, size_t inlen, uint8_t* out,
size_t* outlen, size_t maxlen) = 0;
protected:
void FormatNonce(uint64_t seq, uint8_t *nonce);
bool AeadInner(bool decrypt, void *params, size_t param_length,
const uint8_t *in, size_t inlen, uint8_t *out, size_t *outlen,
void FormatNonce(uint64_t seq, uint8_t* nonce);
bool AeadInner(bool decrypt, void* params, size_t param_length,
const uint8_t* in, size_t inlen, uint8_t* out, size_t* outlen,
size_t maxlen);
CK_MECHANISM_TYPE mech_;
PK11SymKey *key_;
PK11SymKey* key_;
uint8_t iv_[12];
};
@ -43,8 +43,8 @@ class AeadCipherChacha20Poly1305 : public AeadCipher {
AeadCipherChacha20Poly1305() : AeadCipher(CKM_NSS_CHACHA20_POLY1305) {}
protected:
bool Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len, uint64_t seq,
const uint8_t *in, size_t inlen, uint8_t *out, size_t *outlen,
bool Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len, uint64_t seq,
const uint8_t* in, size_t inlen, uint8_t* out, size_t* outlen,
size_t maxlen);
};
@ -53,27 +53,42 @@ class AeadCipherAesGcm : public AeadCipher {
AeadCipherAesGcm() : AeadCipher(CKM_AES_GCM) {}
protected:
bool Aead(bool decrypt, const uint8_t *hdr, size_t hdr_len, uint64_t seq,
const uint8_t *in, size_t inlen, uint8_t *out, size_t *outlen,
bool Aead(bool decrypt, const uint8_t* hdr, size_t hdr_len, uint64_t seq,
const uint8_t* in, size_t inlen, uint8_t* out, size_t* outlen,
size_t maxlen);
};
// Our analog of ssl3CipherSpec
class TlsCipherSpec {
public:
TlsCipherSpec() : epoch_(0), aead_() {}
TlsCipherSpec(bool dtls, uint16_t epoc);
bool SetKeys(SSLCipherSuiteInfo* cipherinfo, PK11SymKey* secret);
bool Init(uint16_t epoch, SSLCipherAlgorithm cipher, PK11SymKey *key,
const uint8_t *iv);
bool Protect(const TlsRecordHeader& header, const DataBuffer& plaintext,
DataBuffer* ciphertext);
bool Unprotect(const TlsRecordHeader& header, const DataBuffer& ciphertext,
DataBuffer* plaintext);
bool Protect(const TlsRecordHeader &header, const DataBuffer &plaintext,
DataBuffer *ciphertext);
bool Unprotect(const TlsRecordHeader &header, const DataBuffer &ciphertext,
DataBuffer *plaintext);
uint16_t epoch() const { return epoch_; }
uint64_t next_in_seqno() const { return in_seqno_; }
void RecordUnprotected(uint64_t seqno) {
// Reordering happens, so don't let this go backwards.
in_seqno_ = (std::max)(in_seqno_, seqno + 1);
}
uint64_t next_out_seqno() { return out_seqno_; }
void RecordProtected() { out_seqno_++; }
void RecordDropped() { record_dropped_ = true; }
bool record_dropped() const { return record_dropped_; }
bool is_protected() const { return aead_ != nullptr; }
private:
bool dtls_;
uint16_t epoch_;
uint64_t in_seqno_;
uint64_t out_seqno_;
bool record_dropped_ = false;
std::unique_ptr<AeadCipher> aead_;
};

View file

@ -1677,6 +1677,92 @@ PK11_MakeKEAPubKey(unsigned char *keyData, int length)
return pubk;
}
SECStatus
SECKEY_SetPublicValue(SECKEYPrivateKey *privKey, SECItem *publicValue)
{
SECStatus rv;
SECKEYPublicKey pubKey;
PLArenaPool *arena;
PK11SlotInfo *slot = privKey->pkcs11Slot;
CK_OBJECT_HANDLE privKeyID = privKey->pkcs11ID;
if (privKey == NULL || publicValue == NULL ||
publicValue->data == NULL || publicValue->len == 0) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
pubKey.arena = NULL;
pubKey.keyType = privKey->keyType;
pubKey.pkcs11Slot = NULL;
pubKey.pkcs11ID = CK_INVALID_HANDLE;
/* can't use PORT_InitCheapArena here becase SECKEY_DestroyPublic is used
* to free it, and it uses PORT_FreeArena which not only frees the
* underlying arena, it also frees the allocated arena struct. */
arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
pubKey.arena = arena;
if (arena == NULL) {
return SECFailure;
}
rv = SECFailure;
switch (privKey->keyType) {
default:
/* error code already set to SECFailure */
break;
case rsaKey:
pubKey.u.rsa.modulus = *publicValue;
rv = PK11_ReadAttribute(slot, privKeyID, CKA_PUBLIC_EXPONENT,
arena, &pubKey.u.rsa.publicExponent);
break;
case dsaKey:
pubKey.u.dsa.publicValue = *publicValue;
rv = PK11_ReadAttribute(slot, privKeyID, CKA_PRIME,
arena, &pubKey.u.dsa.params.prime);
if (rv != SECSuccess) {
break;
}
rv = PK11_ReadAttribute(slot, privKeyID, CKA_SUBPRIME,
arena, &pubKey.u.dsa.params.subPrime);
if (rv != SECSuccess) {
break;
}
rv = PK11_ReadAttribute(slot, privKeyID, CKA_BASE,
arena, &pubKey.u.dsa.params.base);
break;
case dhKey:
pubKey.u.dh.publicValue = *publicValue;
rv = PK11_ReadAttribute(slot, privKeyID, CKA_PRIME,
arena, &pubKey.u.dh.prime);
if (rv != SECSuccess) {
break;
}
rv = PK11_ReadAttribute(slot, privKeyID, CKA_BASE,
arena, &pubKey.u.dh.base);
break;
case ecKey:
pubKey.u.ec.publicValue = *publicValue;
pubKey.u.ec.encoding = ECPoint_Undefined;
pubKey.u.ec.size = 0;
rv = PK11_ReadAttribute(slot, privKeyID, CKA_EC_PARAMS,
arena, &pubKey.u.ec.DEREncodedParams);
break;
}
if (rv == SECSuccess) {
rv = PK11_ImportPublicKey(slot, &pubKey, PR_TRUE);
}
/* Even though pubKey is stored on the stack, we've allocated
* some of it's data from the arena. SECKEY_DestroyPublicKey
* destroys keys by freeing the arena, so this will clean up all
* the data we allocated specifically for the key above. It will
* also free any slot references which we may have picked up in
* PK11_ImportPublicKey. It won't delete the underlying key if
* its a Token/Permanent key (which it will be if
* PK11_ImportPublicKey succeeds). */
SECKEY_DestroyPublicKey(&pubKey);
return rv;
}
/*
* NOTE: This function doesn't return a SECKEYPrivateKey struct to represent
* the new private key object. If it were to create a session object that
@ -1802,12 +1888,6 @@ try_faulty_3des:
nickname, publicValue, isPerm, isPrivate,
key_type, usage, usageCount, wincx);
if (privKey) {
if (privk) {
*privk = privKey;
} else {
SECKEY_DestroyPrivateKey(privKey);
}
privKey = NULL;
rv = SECSuccess;
goto done;
}
@ -1837,6 +1917,25 @@ try_faulty_3des:
rv = SECFailure;
done:
if ((rv == SECSuccess) && isPerm) {
/* If we are importing a token object,
* create the corresponding public key.
* If this fails, just continue as the target
* token simply might not support persistant
* public keys. Such tokens are usable, but
* need to be authenticated before searching
* for user certs. */
(void)SECKEY_SetPublicValue(privKey, publicValue);
}
if (privKey) {
if (privk) {
*privk = privKey;
} else {
SECKEY_DestroyPrivateKey(privKey);
}
privKey = NULL;
}
if (crypto_param != NULL) {
SECITEM_ZfreeItem(crypto_param, PR_TRUE);
}

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 */
}
PORT_Assert(pubKey->u.ec.ecParams.name != ECCurve25519);
/* handle the encoded case */
if ((pubKey->u.ec.publicValue.data[0] == SEC_ASN1_OCTET_STRING) &&
pubKey->u.ec.publicValue.len > keyLen) {
@ -1827,7 +1825,13 @@ sftk_GetPubKey(SFTKObject *object, CK_KEY_TYPE key_type,
SEC_ASN1_GET(SEC_OctetStringTemplate),
&pubKey->u.ec.publicValue);
/* nope, didn't decode correctly */
if ((rv != SECSuccess) || (publicValue.data[0] != EC_POINT_FORM_UNCOMPRESSED) || (publicValue.len != keyLen)) {
if ((rv != SECSuccess) || (publicValue.len != keyLen)) {
crv = CKR_ATTRIBUTE_VALUE_INVALID;
break;
}
/* we don't handle compressed points except in the case of ECCurve25519 */
if ((pubKey->u.ec.ecParams.fieldID.type != ec_field_plain) &&
(publicValue.data[0] != EC_POINT_FORM_UNCOMPRESSED)) {
crv = CKR_ATTRIBUTE_VALUE_INVALID;
break;
}

View file

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

View file

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

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. */
#define SSL_ENABLE_V2_COMPATIBLE_HELLO 38
/* Enables the post-handshake authentication in TLS 1.3. If it is set
* to PR_TRUE, the client will send the "post_handshake_auth"
* extension to indicate that it will process CertificateRequest
* messages after handshake.
*
* This option applies only to clients. For a server, the
* SSL_SendCertificateRequest can be used to request post-handshake
* authentication.
*/
#define SSL_ENABLE_POST_HANDSHAKE_AUTH 39
#ifdef SSL_DEPRECATED_FUNCTION
/* Old deprecated function names */
SSL_IMPORT SECStatus SSL_Enable(PRFileDesc *fd, int option, PRIntn on);

View file

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

View file

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

View file

@ -389,7 +389,6 @@ dtls_GatherData(sslSocket *ss, sslGather *gs, int flags)
* application data is available.
* Returns 0 if ssl3_GatherData hits EOF.
* Returns -1 on read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error.
* Returns -2 on SECWouldBlock return from ssl3_HandleRecord.
*
* Called from ssl_GatherRecord1stHandshake in sslcon.c,
* and from SSL_ForceHandshake in sslsecur.c
@ -422,13 +421,19 @@ ssl3_GatherCompleteHandshake(sslSocket *ss, int flags)
PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
do {
PRBool handleRecordNow = PR_FALSE;
PRBool processingEarlyData;
ssl_GetSSL3HandshakeLock(ss);
processingEarlyData = ss->ssl3.hs.zeroRttState == ssl_0rtt_accepted;
/* If we have a detached record layer, don't ever gather. */
if (ss->recordWriteCallback) {
ssl_ReleaseSSL3HandshakeLock(ss);
PORT_SetError(PR_WOULD_BLOCK_ERROR);
return (int)SECFailure;
}
/* Without this, we may end up wrongly reporting
* SSL_ERROR_RX_UNEXPECTED_* errors if we receive any records from the
* peer while we are waiting to be restarted.
@ -439,93 +444,68 @@ ssl3_GatherCompleteHandshake(sslSocket *ss, int flags)
return (int)SECFailure;
}
/* Treat an empty msgState like a NULL msgState. (Most of the time
* when ssl3_HandleHandshake returns SECWouldBlock, it leaves
* behind a non-NULL but zero-length msgState).
* Test: async_cert_restart_server_sends_hello_request_first_in_separate_record
*/
if (ss->ssl3.hs.msgState.buf) {
if (ss->ssl3.hs.msgState.len == 0) {
ss->ssl3.hs.msgState.buf = NULL;
} else {
handleRecordNow = PR_TRUE;
ssl_ReleaseSSL3HandshakeLock(ss);
/* State for SSLv2 client hello support. */
ssl2Gather ssl2gs = { PR_FALSE, 0 };
ssl2Gather *ssl2gs_ptr = NULL;
/* If we're a server and waiting for a client hello, accept v2. */
if (ss->sec.isServer && ss->opt.enableV2CompatibleHello &&
ss->ssl3.hs.ws == wait_client_hello) {
ssl2gs_ptr = &ssl2gs;
}
/* bring in the next sslv3 record. */
if (ss->recvdCloseNotify) {
/* RFC 5246 Section 7.2.1:
* Any data received after a closure alert is ignored.
*/
return 0;
}
if (!IS_DTLS(ss)) {
/* If we're a server waiting for a ClientHello then pass
* ssl2gs to support SSLv2 ClientHello messages. */
rv = ssl3_GatherData(ss, &ss->gs, flags, ssl2gs_ptr);
} else {
rv = dtls_GatherData(ss, &ss->gs, flags);
/* If we got a would block error, that means that no data was
* available, so we check the timer to see if it's time to
* retransmit */
if (rv == SECFailure &&
(PORT_GetError() == PR_WOULD_BLOCK_ERROR)) {
dtls_CheckTimer(ss);
/* Restore the error in case something succeeded */
PORT_SetError(PR_WOULD_BLOCK_ERROR);
}
}
ssl_ReleaseSSL3HandshakeLock(ss);
if (rv <= 0) {
return rv;
}
if (handleRecordNow) {
/* ssl3_HandleHandshake previously returned SECWouldBlock and the
* as-yet-unprocessed plaintext of that previous handshake record.
* We need to process it now before we overwrite it with the next
* handshake record.
*/
SSL_DBG(("%d: SSL3[%d]: resuming handshake",
SSL_GETPID(), ss->fd));
PORT_Assert(!IS_DTLS(ss));
rv = ssl3_HandleNonApplicationData(ss, ssl_ct_handshake,
0, 0, &ss->gs.buf);
} else {
/* State for SSLv2 client hello support. */
ssl2Gather ssl2gs = { PR_FALSE, 0 };
ssl2Gather *ssl2gs_ptr = NULL;
if (ss->sec.isServer && ss->opt.enableV2CompatibleHello &&
ss->ssl3.hs.ws == wait_client_hello) {
ssl2gs_ptr = &ssl2gs;
}
/* bring in the next sslv3 record. */
if (ss->recvdCloseNotify) {
/* RFC 5246 Section 7.2.1:
* Any data received after a closure alert is ignored.
*/
return 0;
}
if (!IS_DTLS(ss)) {
/* Passing a non-NULL ssl2gs here enables detection of
* SSLv2-compatible ClientHello messages. */
rv = ssl3_GatherData(ss, &ss->gs, flags, ssl2gs_ptr);
} else {
rv = dtls_GatherData(ss, &ss->gs, flags);
/* If we got a would block error, that means that no data was
* available, so we check the timer to see if it's time to
* retransmit */
if (rv == SECFailure &&
(PORT_GetError() == PR_WOULD_BLOCK_ERROR)) {
dtls_CheckTimer(ss);
/* Restore the error in case something succeeded */
PORT_SetError(PR_WOULD_BLOCK_ERROR);
}
}
if (rv <= 0) {
if (ssl2gs.isV2) {
rv = ssl3_HandleV2ClientHello(ss, ss->gs.inbuf.buf,
ss->gs.inbuf.len,
ssl2gs.padding);
if (rv < 0) {
return rv;
}
if (ssl2gs.isV2) {
rv = ssl3_HandleV2ClientHello(ss, ss->gs.inbuf.buf,
ss->gs.inbuf.len,
ssl2gs.padding);
if (rv < 0) {
return rv;
}
} else {
/* decipher it, and handle it if it's a handshake.
* If it's application data, ss->gs.buf will not be empty upon return.
* If it's a change cipher spec, alert, or handshake message,
* ss->gs.buf.len will be 0 when ssl3_HandleRecord returns SECSuccess.
*
* cText only needs to be valid for this next function call, so
* it can borrow gs.hdr.
*/
cText.hdr = ss->gs.hdr;
cText.hdrLen = ss->gs.hdrLen;
cText.buf = &ss->gs.inbuf;
rv = ssl3_HandleRecord(ss, &cText);
}
} else {
/* decipher it, and handle it if it's a handshake.
* If it's application data, ss->gs.buf will not be empty upon return.
* If it's a change cipher spec, alert, or handshake message,
* ss->gs.buf.len will be 0 when ssl3_HandleRecord returns SECSuccess.
*
* cText only needs to be valid for this next function call, so
* it can borrow gs.hdr.
*/
cText.hdr = ss->gs.hdr;
cText.hdrLen = ss->gs.hdrLen;
cText.buf = &ss->gs.inbuf;
rv = ssl3_HandleRecord(ss, &cText);
}
if (rv < 0) {
return ss->recvdCloseNotify ? 0 : rv;
@ -575,7 +555,7 @@ ssl3_GatherCompleteHandshake(sslSocket *ss, int flags)
* delivered to the application before the handshake completes. */
ssl_ReleaseSSL3HandshakeLock(ss);
PORT_SetError(PR_WOULD_BLOCK_ERROR);
return SECWouldBlock;
return -1;
}
ssl_ReleaseSSL3HandshakeLock(ss);
} while (keepGoing);
@ -596,7 +576,6 @@ ssl3_GatherCompleteHandshake(sslSocket *ss, int flags)
* Returns 1 when application data is available.
* Returns 0 if ssl3_GatherData hits EOF.
* Returns -1 on read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error.
* Returns -2 on SECWouldBlock return from ssl3_HandleRecord.
*
* Called from DoRecv in sslsecur.c
* Caller must hold the recv buf lock.
@ -616,3 +595,108 @@ ssl3_GatherAppDataRecord(sslSocket *ss, int flags)
return rv;
}
SECStatus
SSLExp_RecordLayerData(PRFileDesc *fd, PRUint16 epoch,
SSLContentType contentType,
const PRUint8 *data, unsigned int len)
{
SECStatus rv;
sslSocket *ss = ssl_FindSocket(fd);
if (!ss) {
return SECFailure;
}
if (IS_DTLS(ss) || data == NULL || len == 0) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
/* Run any handshake function. If SSL_RecordLayerData is the only way that
* the handshake is driven, then this is necessary to ensure that
* ssl_BeginClientHandshake or ssl_BeginServerHandshake is called. Note that
* the other function that might be set to ss->handshake,
* ssl3_GatherCompleteHandshake, does nothing when this function is used. */
ssl_Get1stHandshakeLock(ss);
rv = ssl_Do1stHandshake(ss);
if (rv != SECSuccess && PORT_GetError() != PR_WOULD_BLOCK_ERROR) {
goto early_loser; /* Rely on the existing code. */
}
/* Don't allow application data before handshake completion. */
if (contentType == ssl_ct_application_data && !ss->firstHsDone) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
goto early_loser;
}
/* Then we can validate the epoch. */
PRErrorCode epochError;
ssl_GetSpecReadLock(ss);
if (epoch < ss->ssl3.crSpec->epoch) {
epochError = SEC_ERROR_INVALID_ARGS; /* Too c/old. */
} else if (epoch > ss->ssl3.crSpec->epoch) {
epochError = PR_WOULD_BLOCK_ERROR; /* Too warm/new. */
} else {
epochError = 0; /* Just right. */
}
ssl_ReleaseSpecReadLock(ss);
if (epochError) {
PORT_SetError(epochError);
goto early_loser;
}
/* If the handshake is still running, we need to run that. */
ssl_Get1stHandshakeLock(ss);
rv = ssl_Do1stHandshake(ss);
if (rv != SECSuccess && PORT_GetError() != PR_WOULD_BLOCK_ERROR) {
ssl_Release1stHandshakeLock(ss);
return SECFailure;
}
/* Finally, save the data... */
ssl_GetRecvBufLock(ss);
rv = sslBuffer_Append(&ss->gs.buf, data, len);
if (rv != SECSuccess) {
goto loser;
}
/* ...and process it. Just saving application data is enough for it to be
* available to PR_Read(). */
if (contentType != ssl_ct_application_data) {
rv = ssl3_HandleNonApplicationData(ss, contentType, 0, 0, &ss->gs.buf);
if (rv != SECSuccess) {
goto loser;
}
}
ssl_ReleaseRecvBufLock(ss);
ssl_Release1stHandshakeLock(ss);
return SECSuccess;
loser:
/* Make sure that any data is not used again. */
ss->gs.buf.len = 0;
ssl_ReleaseRecvBufLock(ss);
early_loser:
ssl_Release1stHandshakeLock(ss);
return SECFailure;
}
SECStatus
SSLExp_GetCurrentEpoch(PRFileDesc *fd, PRUint16 *readEpoch,
PRUint16 *writeEpoch)
{
sslSocket *ss = ssl_FindSocket(fd);
if (!ss) {
return SECFailure;
}
ssl_GetSpecReadLock(ss);
if (readEpoch) {
*readEpoch = ss->ssl3.crSpec->epoch;
}
if (writeEpoch) {
*writeEpoch = ss->ssl3.cwSpec->epoch;
}
ssl_ReleaseSpecReadLock(ss);
return SECSuccess;
}

View file

@ -44,17 +44,13 @@ const char *ssl_version = "SECURITY_VERSION:"
* This function acquires and releases the RecvBufLock.
*
* returns SECSuccess for success.
* returns SECWouldBlock when that value is returned by
* ssl3_GatherCompleteHandshake().
* returns SECFailure on all other errors.
* returns SECFailure on error, setting PR_WOULD_BLOCK_ERROR if only blocked.
*
* The gather functions called by ssl_GatherRecord1stHandshake are expected
* to return values interpreted as follows:
* 1 : the function completed without error.
* 0 : the function read EOF.
* -1 : read error, or PR_WOULD_BLOCK_ERROR, or handleRecord error.
* -2 : the function wants ssl_GatherRecord1stHandshake to be called again
* immediately, by ssl_Do1stHandshake.
*
* This code is similar to, and easily confused with, DoRecv() in sslsecur.c
*
@ -82,16 +78,14 @@ ssl_GatherRecord1stHandshake(sslSocket *ss)
ssl_ReleaseRecvBufLock(ss);
if (rv <= 0) {
if (rv == SECWouldBlock) {
/* Progress is blocked waiting for callback completion. */
SSL_TRC(10, ("%d: SSL[%d]: handshake blocked (need %d)",
SSL_GETPID(), ss->fd, ss->gs.remainder));
return SECWouldBlock;
}
if (rv == 0) {
/* EOF. Loser */
PORT_SetError(PR_END_OF_FILE_ERROR);
}
if (PORT_GetError() == PR_WOULD_BLOCK_ERROR) {
SSL_TRC(10, ("%d: SSL[%d]: handshake blocked (need %d)",
SSL_GETPID(), ss->fd, ss->gs.remainder));
}
return SECFailure; /* rv is < 0 here. */
}

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

View file

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

View file

@ -350,6 +350,27 @@ typedef SSLHelloRetryRequestAction(PR_CALLBACK *SSLHelloRetryRequestCallback)(
(PRFileDesc * _fd, PRBool _requestUpdate), \
(fd, requestUpdate))
/* This function allows a server application to trigger
* re-authentication (TLS 1.3 only) after handshake.
*
* This function will cause a CertificateRequest message to be sent by
* a server. This can be called once at a time, and is not allowed
* until an answer is received.
*
* The AuthCertificateCallback is called when the answer is received.
* If the answer is accepted by the server, the value returned by
* SSL_PeerCertificate() is replaced. If you need to remember all the
* certificates, you will need to call SSL_PeerCertificate() and save
* what you get before calling this.
*
* If the AuthCertificateCallback returns SECFailure, the connection
* is aborted.
*/
#define SSL_SendCertificateRequest(fd) \
SSL_EXPERIMENTAL_API("SSL_SendCertificateRequest", \
(PRFileDesc * _fd), \
(fd))
/*
* Session cache API.
*/
@ -511,6 +532,100 @@ typedef SECStatus(PR_CALLBACK *SSLResumptionTokenCallback)(
group, pubKey, pad, notBefore, notAfter, \
out, outlen, maxlen))
/* SSL_SetSecretCallback installs a callback that TLS calls when it installs new
* traffic secrets.
*
* SSLSecretCallback is called with the current epoch and the corresponding
* secret; this matches the epoch used in DTLS 1.3, even if the socket is
* operating in stream mode:
*
* - client_early_traffic_secret corresponds to epoch 1
* - {client|server}_handshake_traffic_secret is epoch 2
* - {client|server}_application_traffic_secret_{N} is epoch 3+N
*
* The callback is invoked separately for read secrets (client secrets on the
* server; server secrets on the client), and write secrets.
*
* This callback is only called if (D)TLS 1.3 is negotiated.
*/
typedef void(PR_CALLBACK *SSLSecretCallback)(
PRFileDesc *fd, PRUint16 epoch, SSLSecretDirection dir, PK11SymKey *secret,
void *arg);
#define SSL_SecretCallback(fd, cb, arg) \
SSL_EXPERIMENTAL_API("SSL_SecretCallback", \
(PRFileDesc * _fd, SSLSecretCallback _cb, void *_arg), \
(fd, cb, arg))
/* SSL_RecordLayerWriteCallback() is used to replace the TLS record layer. This
* function installs a callback that TLS calls when it would otherwise encrypt
* and write a record to the underlying NSPR IO layer. The application is
* responsible for ensuring that these records are encrypted and written.
*
* Calling this API also disables reads from the underlying NSPR layer. The
* application is expected to push data when it is available using
* SSL_RecordLayerData().
*
* When data would be written, the provided SSLRecordWriteCallback with the
* epoch, TLS content type, and the data. The data provided to the callback is
* not split into record-sized writes. If the callback returns SECFailure, the
* write will be considered to have failed; in particular, PR_WOULD_BLOCK_ERROR
* is not handled specially.
*
* If TLS 1.3 is in use, the epoch indicates the expected level of protection
* that the record would receive, this matches that used in DTLS 1.3:
*
* - epoch 0 corresponds to no record protection
* - epoch 1 corresponds to 0-RTT
* - epoch 2 corresponds to TLS handshake
* - epoch 3 and higher are application data
*
* Prior versions of TLS use epoch 1 and higher for application data.
*
* This API is not supported for DTLS.
*/
typedef SECStatus(PR_CALLBACK *SSLRecordWriteCallback)(
PRFileDesc *fd, PRUint16 epoch, SSLContentType contentType,
const PRUint8 *data, unsigned int len, void *arg);
#define SSL_RecordLayerWriteCallback(fd, writeCb, arg) \
SSL_EXPERIMENTAL_API("SSL_RecordLayerWriteCallback", \
(PRFileDesc * _fd, SSLRecordWriteCallback _wCb, \
void *_arg), \
(fd, writeCb, arg))
/* SSL_RecordLayerData() is used to provide new data to TLS. The application
* indicates the epoch (see the description of SSL_RecordLayerWriteCallback()),
* content type, and the data that was received. The application is responsible
* for removing any encryption or other protection before passing data to this
* function.
*
* This returns SECSuccess if the data was successfully processed. If this
* function is used to drive the handshake and the caller needs to know when the
* handshake is complete, a call to SSL_ForceHandshake will return SECSuccess
* when the handshake is complete.
*
* This API is not supported for DTLS sockets.
*/
#define SSL_RecordLayerData(fd, epoch, ct, data, len) \
SSL_EXPERIMENTAL_API("SSL_RecordLayerData", \
(PRFileDesc * _fd, PRUint16 _epoch, \
SSLContentType _contentType, \
const PRUint8 *_data, unsigned int _len), \
(fd, epoch, ct, data, len))
/*
* SSL_GetCurrentEpoch() returns the read and write epochs that the socket is
* currently using. NULL values for readEpoch or writeEpoch are ignored.
*
* See SSL_RecordLayerWriteCallback() for details on epochs.
*/
#define SSL_GetCurrentEpoch(fd, readEpoch, writeEpoch) \
SSL_EXPERIMENTAL_API("SSL_GetCurrentEpoch", \
(PRFileDesc * _fd, PRUint16 * _readEpoch, \
PRUint16 * _writeEpoch), \
(fd, readEpoch, writeEpoch))
/* Deprecated experimental APIs */
#define SSL_UseAltServerHelloType(fd, enable) SSL_DEPRECATED_EXPERIMENTAL_API

View file

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

View file

@ -150,6 +150,7 @@ SSL_GetPreliminaryChannelInfo(PRFileDesc *fd,
} else {
inf.maxEarlyDataSize = 0;
}
inf.zeroRttCipherSuite = ss->ssl3.hs.zeroRttSuite;
memcpy(info, &inf, inf.length);
return SECSuccess;
@ -234,89 +235,89 @@ SSL_GetPreliminaryChannelInfo(PRFileDesc *fd,
static const SSLCipherSuiteInfo suiteInfo[] = {
/* <------ Cipher suite --------------------> <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_CHACHA20_POLY1305_SHA256), S_ANY, K_ANY, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_ANY },
{ 0, CS_(TLS_AES_256_GCM_SHA384), S_ANY, K_ANY, C_AESGCM, B_256, M_AEAD_128, F_NFIPS_STD, A_ANY },
{ 0, CS_(TLS_AES_128_GCM_SHA256), S_ANY, K_ANY, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_ANY, ssl_hash_sha256 },
{ 0, CS_(TLS_CHACHA20_POLY1305_SHA256), S_ANY, K_ANY, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_ANY, ssl_hash_sha256 },
{ 0, CS_(TLS_AES_256_GCM_SHA384), S_ANY, K_ANY, C_AESGCM, B_256, M_AEAD_128, F_NFIPS_STD, A_ANY, ssl_hash_sha384 },
{ 0, CS(RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_RSA, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_RSAD },
{ 0, CS(DHE_RSA_WITH_CHACHA20_POLY1305_SHA256), S_RSA, K_DHE, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_RSAS },
{ 0, CS(RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_RSA, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_RSAD, ssl_hash_sha256 },
{ 0, CS(DHE_RSA_WITH_CHACHA20_POLY1305_SHA256), S_RSA, K_DHE, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_RSAS, ssl_hash_sha256 },
{ 0, CS(DHE_RSA_WITH_CAMELLIA_256_CBC_SHA), S_RSA, K_DHE, C_CAMELLIA, B_256, M_SHA, F_NFIPS_STD, A_RSAS },
{ 0, CS(DHE_DSS_WITH_CAMELLIA_256_CBC_SHA), S_DSA, K_DHE, C_CAMELLIA, B_256, M_SHA, F_NFIPS_STD, A_DSA },
{ 0, CS(DHE_RSA_WITH_AES_256_CBC_SHA256), S_RSA, K_DHE, C_AES, B_256, M_SHA256, F_FIPS_STD, A_RSAS },
{ 0, CS(DHE_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_DHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_RSAS },
{ 0, CS(DHE_DSS_WITH_AES_256_CBC_SHA), S_DSA, K_DHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_DSA },
{ 0, CS(DHE_DSS_WITH_AES_256_CBC_SHA256), S_DSA, K_DHE, C_AES, B_256, M_SHA256, F_FIPS_STD, A_DSA },
{ 0, CS(RSA_WITH_CAMELLIA_256_CBC_SHA), S_RSA, K_RSA, C_CAMELLIA, B_256, M_SHA, F_NFIPS_STD, A_RSAD },
{ 0, CS(RSA_WITH_AES_256_CBC_SHA256), S_RSA, K_RSA, C_AES, B_256, M_SHA256, F_FIPS_STD, A_RSAD },
{ 0, CS(RSA_WITH_AES_256_CBC_SHA), S_RSA, K_RSA, C_AES, B_256, M_SHA, F_FIPS_STD, A_RSAD },
{ 0, CS(DHE_RSA_WITH_CAMELLIA_256_CBC_SHA), S_RSA, K_DHE, C_CAMELLIA, B_256, M_SHA, F_NFIPS_STD, A_RSAS, ssl_hash_none },
{ 0, CS(DHE_DSS_WITH_CAMELLIA_256_CBC_SHA), S_DSA, K_DHE, C_CAMELLIA, B_256, M_SHA, F_NFIPS_STD, A_DSA, ssl_hash_none },
{ 0, CS(DHE_RSA_WITH_AES_256_CBC_SHA256), S_RSA, K_DHE, C_AES, B_256, M_SHA256, F_FIPS_STD, A_RSAS, ssl_hash_sha256 },
{ 0, CS(DHE_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_DHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_RSAS, ssl_hash_none },
{ 0, CS(DHE_DSS_WITH_AES_256_CBC_SHA), S_DSA, K_DHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_DSA, ssl_hash_none },
{ 0, CS(DHE_DSS_WITH_AES_256_CBC_SHA256), S_DSA, K_DHE, C_AES, B_256, M_SHA256, F_FIPS_STD, A_DSA, ssl_hash_sha256 },
{ 0, CS(RSA_WITH_CAMELLIA_256_CBC_SHA), S_RSA, K_RSA, C_CAMELLIA, B_256, M_SHA, F_NFIPS_STD, A_RSAD, ssl_hash_none },
{ 0, CS(RSA_WITH_AES_256_CBC_SHA256), S_RSA, K_RSA, C_AES, B_256, M_SHA256, F_FIPS_STD, A_RSAD, ssl_hash_sha256 },
{ 0, CS(RSA_WITH_AES_256_CBC_SHA), S_RSA, K_RSA, C_AES, B_256, M_SHA, F_FIPS_STD, A_RSAD, ssl_hash_none },
{ 0, CS(DHE_RSA_WITH_CAMELLIA_128_CBC_SHA), S_RSA, K_DHE, C_CAMELLIA, B_128, M_SHA, F_NFIPS_STD, A_RSAS },
{ 0, CS(DHE_DSS_WITH_CAMELLIA_128_CBC_SHA), S_DSA, K_DHE, C_CAMELLIA, B_128, M_SHA, F_NFIPS_STD, A_DSA },
{ 0, CS(DHE_DSS_WITH_RC4_128_SHA), S_DSA, K_DHE, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_DSA },
{ 0, CS(DHE_RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_DHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_RSAS },
{ 0, CS(DHE_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_DHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_RSAS },
{ 0, CS(DHE_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_DHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_RSAS },
{ 0, CS(DHE_DSS_WITH_AES_128_GCM_SHA256), S_DSA, K_DHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_DSA },
{ 0, CS(DHE_DSS_WITH_AES_128_CBC_SHA), S_DSA, K_DHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_DSA },
{ 0, CS(DHE_DSS_WITH_AES_128_CBC_SHA256), S_DSA, K_DHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_DSA },
{ 0, CS(RSA_WITH_SEED_CBC_SHA), S_RSA, K_RSA, C_SEED, B_128, M_SHA, F_FIPS_STD, A_RSAD },
{ 0, CS(RSA_WITH_CAMELLIA_128_CBC_SHA), S_RSA, K_RSA, C_CAMELLIA, B_128, M_SHA, F_NFIPS_STD, A_RSAD },
{ 0, CS(RSA_WITH_RC4_128_SHA), S_RSA, K_RSA, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_RSAD },
{ 0, CS(RSA_WITH_RC4_128_MD5), S_RSA, K_RSA, C_RC4, B_128, M_MD5, F_NFIPS_STD, A_RSAD },
{ 0, CS(RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_RSA, C_AES, B_128, M_SHA256, F_FIPS_STD, A_RSAD },
{ 0, CS(RSA_WITH_AES_128_CBC_SHA), S_RSA, K_RSA, C_AES, B_128, M_SHA, F_FIPS_STD, A_RSAD },
{ 0, CS(DHE_RSA_WITH_CAMELLIA_128_CBC_SHA), S_RSA, K_DHE, C_CAMELLIA, B_128, M_SHA, F_NFIPS_STD, A_RSAS, ssl_hash_none },
{ 0, CS(DHE_DSS_WITH_CAMELLIA_128_CBC_SHA), S_DSA, K_DHE, C_CAMELLIA, B_128, M_SHA, F_NFIPS_STD, A_DSA, ssl_hash_none },
{ 0, CS(DHE_DSS_WITH_RC4_128_SHA), S_DSA, K_DHE, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_DSA, ssl_hash_none },
{ 0, CS(DHE_RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_DHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_RSAS, ssl_hash_sha256 },
{ 0, CS(DHE_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_DHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_RSAS, ssl_hash_sha256 },
{ 0, CS(DHE_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_DHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_RSAS, ssl_hash_none },
{ 0, CS(DHE_DSS_WITH_AES_128_GCM_SHA256), S_DSA, K_DHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_DSA, ssl_hash_sha256 },
{ 0, CS(DHE_DSS_WITH_AES_128_CBC_SHA), S_DSA, K_DHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_DSA, ssl_hash_none },
{ 0, CS(DHE_DSS_WITH_AES_128_CBC_SHA256), S_DSA, K_DHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_DSA, ssl_hash_sha256 },
{ 0, CS(RSA_WITH_SEED_CBC_SHA), S_RSA, K_RSA, C_SEED, B_128, M_SHA, F_FIPS_STD, A_RSAD, ssl_hash_none },
{ 0, CS(RSA_WITH_CAMELLIA_128_CBC_SHA), S_RSA, K_RSA, C_CAMELLIA, B_128, M_SHA, F_NFIPS_STD, A_RSAD, ssl_hash_none },
{ 0, CS(RSA_WITH_RC4_128_SHA), S_RSA, K_RSA, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_RSAD, ssl_hash_none },
{ 0, CS(RSA_WITH_RC4_128_MD5), S_RSA, K_RSA, C_RC4, B_128, M_MD5, F_NFIPS_STD, A_RSAD, ssl_hash_none },
{ 0, CS(RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_RSA, C_AES, B_128, M_SHA256, F_FIPS_STD, A_RSAD, ssl_hash_sha256 },
{ 0, CS(RSA_WITH_AES_128_CBC_SHA), S_RSA, K_RSA, C_AES, B_128, M_SHA, F_FIPS_STD, A_RSAD, ssl_hash_none },
{ 0, CS(DHE_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_DHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_RSAS },
{ 0, CS(DHE_DSS_WITH_3DES_EDE_CBC_SHA), S_DSA, K_DHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_DSA },
{ 0, CS(RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_RSA, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_RSAD },
{ 0, CS(DHE_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_DHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_RSAS, ssl_hash_none },
{ 0, CS(DHE_DSS_WITH_3DES_EDE_CBC_SHA), S_DSA, K_DHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_DSA, ssl_hash_none },
{ 0, CS(RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_RSA, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_RSAD, ssl_hash_none },
{ 0, CS(DHE_RSA_WITH_DES_CBC_SHA), S_RSA, K_DHE, C_DES, B_DES, M_SHA, F_NFIPS_STD, A_RSAS },
{ 0, CS(DHE_DSS_WITH_DES_CBC_SHA), S_DSA, K_DHE, C_DES, B_DES, M_SHA, F_NFIPS_STD, A_DSA },
{ 0, CS(RSA_WITH_DES_CBC_SHA), S_RSA, K_RSA, C_DES, B_DES, M_SHA, F_NFIPS_STD, A_RSAD },
{ 0, CS(DHE_RSA_WITH_DES_CBC_SHA), S_RSA, K_DHE, C_DES, B_DES, M_SHA, F_NFIPS_STD, A_RSAS, ssl_hash_none },
{ 0, CS(DHE_DSS_WITH_DES_CBC_SHA), S_DSA, K_DHE, C_DES, B_DES, M_SHA, F_NFIPS_STD, A_DSA, ssl_hash_none },
{ 0, CS(RSA_WITH_DES_CBC_SHA), S_RSA, K_RSA, C_DES, B_DES, M_SHA, F_NFIPS_STD, A_RSAD, ssl_hash_none },
{ 0, CS(RSA_WITH_NULL_SHA256), S_RSA, K_RSA, C_NULL, B_0, M_SHA256, F_EXPORT, A_RSAD },
{ 0, CS(RSA_WITH_NULL_SHA), S_RSA, K_RSA, C_NULL, B_0, M_SHA, F_EXPORT, A_RSAD },
{ 0, CS(RSA_WITH_NULL_MD5), S_RSA, K_RSA, C_NULL, B_0, M_MD5, F_EXPORT, A_RSAD },
{ 0, CS(RSA_WITH_NULL_SHA256), S_RSA, K_RSA, C_NULL, B_0, M_SHA256, F_EXPORT, A_RSAD, ssl_hash_sha256 },
{ 0, CS(RSA_WITH_NULL_SHA), S_RSA, K_RSA, C_NULL, B_0, M_SHA, F_EXPORT, A_RSAD, ssl_hash_none },
{ 0, CS(RSA_WITH_NULL_MD5), S_RSA, K_RSA, C_NULL, B_0, M_MD5, F_EXPORT, A_RSAD, ssl_hash_none },
/* ECC cipher suites */
{ 0, CS(ECDHE_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_ECDHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_RSAS },
{ 0, CS(ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), S_ECDSA, K_ECDHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_ECDSA },
{ 0, CS(ECDH_ECDSA_WITH_NULL_SHA), S_ECDSA, K_ECDH, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_ECDH_E },
{ 0, CS(ECDH_ECDSA_WITH_RC4_128_SHA), S_ECDSA, K_ECDH, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_ECDH_E },
{ 0, CS(ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA), S_ECDSA, K_ECDH, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_ECDH_E },
{ 0, CS(ECDH_ECDSA_WITH_AES_128_CBC_SHA), S_ECDSA, K_ECDH, C_AES, B_128, M_SHA, F_FIPS_STD, A_ECDH_E },
{ 0, CS(ECDH_ECDSA_WITH_AES_256_CBC_SHA), S_ECDSA, K_ECDH, C_AES, B_256, M_SHA, F_FIPS_STD, A_ECDH_E },
{ 0, CS(ECDHE_RSA_WITH_AES_128_GCM_SHA256), S_RSA, K_ECDHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_RSAS, ssl_hash_sha256 },
{ 0, CS(ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), S_ECDSA, K_ECDHE, C_AESGCM, B_128, M_AEAD_128, F_FIPS_STD, A_ECDSA, ssl_hash_sha256 },
{ 0, CS(ECDH_ECDSA_WITH_NULL_SHA), S_ECDSA, K_ECDH, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_ECDH_E, ssl_hash_none },
{ 0, CS(ECDH_ECDSA_WITH_RC4_128_SHA), S_ECDSA, K_ECDH, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_ECDH_E, ssl_hash_none },
{ 0, CS(ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA), S_ECDSA, K_ECDH, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_ECDH_E, ssl_hash_none },
{ 0, CS(ECDH_ECDSA_WITH_AES_128_CBC_SHA), S_ECDSA, K_ECDH, C_AES, B_128, M_SHA, F_FIPS_STD, A_ECDH_E, ssl_hash_none },
{ 0, CS(ECDH_ECDSA_WITH_AES_256_CBC_SHA), S_ECDSA, K_ECDH, C_AES, B_256, M_SHA, F_FIPS_STD, A_ECDH_E, ssl_hash_none },
{ 0, CS(ECDHE_ECDSA_WITH_NULL_SHA), S_ECDSA, K_ECDHE, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_ECDSA },
{ 0, CS(ECDHE_ECDSA_WITH_RC4_128_SHA), S_ECDSA, K_ECDHE, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_ECDSA },
{ 0, CS(ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA), S_ECDSA, K_ECDHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_ECDSA },
{ 0, CS(ECDHE_ECDSA_WITH_AES_128_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_ECDSA },
{ 0, CS(ECDHE_ECDSA_WITH_AES_128_CBC_SHA256), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_ECDSA },
{ 0, CS(ECDHE_ECDSA_WITH_AES_256_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_ECDSA },
{ 0, CS(ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256), S_ECDSA, K_ECDHE, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_ECDSA },
{ 0, CS(ECDHE_ECDSA_WITH_NULL_SHA), S_ECDSA, K_ECDHE, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_ECDSA, ssl_hash_none },
{ 0, CS(ECDHE_ECDSA_WITH_RC4_128_SHA), S_ECDSA, K_ECDHE, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_ECDSA, ssl_hash_none },
{ 0, CS(ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA), S_ECDSA, K_ECDHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_ECDSA, ssl_hash_none },
{ 0, CS(ECDHE_ECDSA_WITH_AES_128_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_ECDSA, ssl_hash_none },
{ 0, CS(ECDHE_ECDSA_WITH_AES_128_CBC_SHA256), S_ECDSA, K_ECDHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_ECDSA, ssl_hash_sha256 },
{ 0, CS(ECDHE_ECDSA_WITH_AES_256_CBC_SHA), S_ECDSA, K_ECDHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_ECDSA, ssl_hash_none },
{ 0, CS(ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256), S_ECDSA, K_ECDHE, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_ECDSA, ssl_hash_sha256 },
{ 0, CS(ECDH_RSA_WITH_NULL_SHA), S_RSA, K_ECDH, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_ECDH_R },
{ 0, CS(ECDH_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDH, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_ECDH_R },
{ 0, CS(ECDH_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_ECDH, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_ECDH_R },
{ 0, CS(ECDH_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_ECDH, C_AES, B_128, M_SHA, F_FIPS_STD, A_ECDH_R },
{ 0, CS(ECDH_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_ECDH, C_AES, B_256, M_SHA, F_FIPS_STD, A_ECDH_R },
{ 0, CS(ECDH_RSA_WITH_NULL_SHA), S_RSA, K_ECDH, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_ECDH_R, ssl_hash_none },
{ 0, CS(ECDH_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDH, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_ECDH_R, ssl_hash_none },
{ 0, CS(ECDH_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_ECDH, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_ECDH_R, ssl_hash_none },
{ 0, CS(ECDH_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_ECDH, C_AES, B_128, M_SHA, F_FIPS_STD, A_ECDH_R, ssl_hash_none },
{ 0, CS(ECDH_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_ECDH, C_AES, B_256, M_SHA, F_FIPS_STD, A_ECDH_R, ssl_hash_none },
{ 0, CS(ECDHE_RSA_WITH_NULL_SHA), S_RSA, K_ECDHE, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_RSAS },
{ 0, CS(ECDHE_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDHE, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_RSAS },
{ 0, CS(ECDHE_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_ECDHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_RSAS },
{ 0, CS(ECDHE_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_ECDHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_RSAS },
{ 0, CS(ECDHE_RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_ECDHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_RSAS },
{ 0, CS(ECDHE_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_ECDHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_RSAS },
{ 0, CS(ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), S_RSA, K_ECDHE, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_RSAS },
{ 0, CS(ECDHE_RSA_WITH_AES_256_CBC_SHA384), S_RSA, K_ECDHE, C_AES, B_256, M_SHA384, F_FIPS_STD, A_RSAS },
{ 0, CS(ECDHE_ECDSA_WITH_AES_256_CBC_SHA384), S_ECDSA, K_ECDHE, C_AES, B_256, M_SHA384, F_FIPS_STD, A_ECDSA },
{ 0, CS(ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), S_ECDSA, K_ECDHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_ECDSA },
{ 0, CS(ECDHE_RSA_WITH_AES_256_GCM_SHA384), S_RSA, K_ECDHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_RSAS },
{ 0, CS(ECDHE_RSA_WITH_NULL_SHA), S_RSA, K_ECDHE, C_NULL, B_0, M_SHA, F_NFIPS_STD, A_RSAS, ssl_hash_none },
{ 0, CS(ECDHE_RSA_WITH_RC4_128_SHA), S_RSA, K_ECDHE, C_RC4, B_128, M_SHA, F_NFIPS_STD, A_RSAS, ssl_hash_none },
{ 0, CS(ECDHE_RSA_WITH_3DES_EDE_CBC_SHA), S_RSA, K_ECDHE, C_3DES, B_3DES, M_SHA, F_FIPS_STD, A_RSAS, ssl_hash_none },
{ 0, CS(ECDHE_RSA_WITH_AES_128_CBC_SHA), S_RSA, K_ECDHE, C_AES, B_128, M_SHA, F_FIPS_STD, A_RSAS, ssl_hash_none },
{ 0, CS(ECDHE_RSA_WITH_AES_128_CBC_SHA256), S_RSA, K_ECDHE, C_AES, B_128, M_SHA256, F_FIPS_STD, A_RSAS, ssl_hash_sha256 },
{ 0, CS(ECDHE_RSA_WITH_AES_256_CBC_SHA), S_RSA, K_ECDHE, C_AES, B_256, M_SHA, F_FIPS_STD, A_RSAS, ssl_hash_none },
{ 0, CS(ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), S_RSA, K_ECDHE, C_CHACHA20, B_256, M_AEAD_128, F_NFIPS_STD, A_RSAS, ssl_hash_sha256 },
{ 0, CS(ECDHE_RSA_WITH_AES_256_CBC_SHA384), S_RSA, K_ECDHE, C_AES, B_256, M_SHA384, F_FIPS_STD, A_RSAS, ssl_hash_sha384 },
{ 0, CS(ECDHE_ECDSA_WITH_AES_256_CBC_SHA384), S_ECDSA, K_ECDHE, C_AES, B_256, M_SHA384, F_FIPS_STD, A_ECDSA, ssl_hash_sha384 },
{ 0, CS(ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), S_ECDSA, K_ECDHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_ECDSA, ssl_hash_sha384 },
{ 0, CS(ECDHE_RSA_WITH_AES_256_GCM_SHA384), S_RSA, K_ECDHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_RSAS, ssl_hash_sha384 },
{ 0, CS(DHE_DSS_WITH_AES_256_GCM_SHA384), S_DSA, K_DHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_DSA },
{ 0, CS(DHE_RSA_WITH_AES_256_GCM_SHA384), S_RSA, K_DHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_RSAS },
{ 0, CS(RSA_WITH_AES_256_GCM_SHA384), S_RSA, K_RSA, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_RSAD },
{ 0, CS(DHE_DSS_WITH_AES_256_GCM_SHA384), S_DSA, K_DHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_DSA, ssl_hash_sha384 },
{ 0, CS(DHE_RSA_WITH_AES_256_GCM_SHA384), S_RSA, K_DHE, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_RSAS, ssl_hash_sha384 },
{ 0, CS(RSA_WITH_AES_256_GCM_SHA384), S_RSA, K_RSA, C_AESGCM, B_256, M_AEAD_128, F_FIPS_STD, A_RSAD, ssl_hash_sha384 },
};
#define NUM_SUITEINFOS ((sizeof suiteInfo) / (sizeof suiteInfo[0]))

View file

@ -16,32 +16,7 @@
#include "nss.h" /* for NSS_RegisterShutdown */
#include "prinit.h" /* for PR_CallOnceWithArg */
/* Returns a SECStatus: SECSuccess or SECFailure, NOT SECWouldBlock.
*
* Currently, the list of functions called through ss->handshake is:
*
* In sslsocks.c:
* SocksGatherRecord
* SocksHandleReply
* SocksStartGather
*
* In sslcon.c:
* ssl_GatherRecord1stHandshake
* ssl_BeginClientHandshake
* ssl_BeginServerHandshake
*
* The ss->handshake function returns SECWouldBlock if it was returned by
* one of the callback functions, via one of these paths:
*
* - ssl_GatherRecord1stHandshake() -> ssl3_GatherCompleteHandshake() ->
* ssl3_HandleRecord() -> ssl3_HandleHandshake() ->
* ssl3_HandleHandshakeMessage() -> ssl3_HandleCertificate() ->
* ss->handleBadCert()
*
* - ssl_GatherRecord1stHandshake() -> ssl3_GatherCompleteHandshake() ->
* ssl3_HandleRecord() -> ssl3_HandleHandshake() ->
* ssl3_HandleHandshakeMessage() -> ssl3_HandleCertificateRequest() ->
* ss->getClientAuthData()
/* Step through the handshake functions.
*
* Called from: SSL_ForceHandshake (below),
* ssl_SecureRecv (below) and
@ -52,10 +27,10 @@
*
* Caller must hold the (write) handshakeLock.
*/
int
SECStatus
ssl_Do1stHandshake(sslSocket *ss)
{
int rv = SECSuccess;
SECStatus rv = SECSuccess;
while (ss->handshake && rv == SECSuccess) {
PORT_Assert(ss->opt.noLocks || ssl_Have1stHandshakeLock(ss));
@ -70,10 +45,6 @@ ssl_Do1stHandshake(sslSocket *ss)
PORT_Assert(ss->opt.noLocks || !ssl_HaveXmitBufLock(ss));
PORT_Assert(ss->opt.noLocks || !ssl_HaveSSL3HandshakeLock(ss));
if (rv == SECWouldBlock) {
PORT_SetError(PR_WOULD_BLOCK_ERROR);
rv = SECFailure;
}
return rv;
}
@ -106,8 +77,8 @@ ssl_FinishHandshake(sslSocket *ss)
static SECStatus
ssl3_AlwaysBlock(sslSocket *ss)
{
PORT_SetError(PR_WOULD_BLOCK_ERROR); /* perhaps redundant. */
return SECWouldBlock;
PORT_SetError(PR_WOULD_BLOCK_ERROR);
return SECFailure;
}
/*
@ -400,10 +371,13 @@ SSL_ForceHandshake(PRFileDesc *fd)
ssl_ReleaseRecvBufLock(ss);
if (gatherResult > 0) {
rv = SECSuccess;
} else if (gatherResult == 0) {
PORT_SetError(PR_END_OF_FILE_ERROR);
} else if (gatherResult == SECWouldBlock) {
PORT_SetError(PR_WOULD_BLOCK_ERROR);
} else {
if (gatherResult == 0) {
PORT_SetError(PR_END_OF_FILE_ERROR);
}
/* We can rely on ssl3_GatherCompleteHandshake to set
* PR_WOULD_BLOCK_ERROR as needed here. */
rv = SECFailure;
}
} else {
PORT_Assert(!ss->firstHsDone);
@ -515,8 +489,7 @@ DoRecv(sslSocket *ss, unsigned char *out, int len, int flags)
SSL_GETPID(), ss->fd));
goto done;
}
if ((rv != SECWouldBlock) &&
(PR_GetError() != PR_WOULD_BLOCK_ERROR)) {
if (PR_GetError() != PR_WOULD_BLOCK_ERROR) {
/* Some random error */
goto done;
}
@ -741,7 +714,7 @@ ssl_SecureShutdown(sslSocket *ss, int nsprHow)
/************************************************************************/
static SECStatus
tls13_CheckKeyUpdate(sslSocket *ss, CipherSpecDirection dir)
tls13_CheckKeyUpdate(sslSocket *ss, SSLSecretDirection dir)
{
PRBool keyUpdate;
ssl3CipherSpec *spec;
@ -765,7 +738,7 @@ tls13_CheckKeyUpdate(sslSocket *ss, CipherSpecDirection dir)
* having the write margin larger reduces the number of times that a
* KeyUpdate is sent by a reader. */
ssl_GetSpecReadLock(ss);
if (dir == CipherSpecRead) {
if (dir == ssl_secret_read) {
spec = ss->ssl3.crSpec;
margin = spec->cipherDef->max_records / 8;
} else {
@ -781,10 +754,10 @@ tls13_CheckKeyUpdate(sslSocket *ss, CipherSpecDirection dir)
SSL_TRC(5, ("%d: SSL[%d]: automatic key update at %llx for %s cipher spec",
SSL_GETPID(), ss->fd, seqNum,
(dir == CipherSpecRead) ? "read" : "write"));
(dir == ssl_secret_read) ? "read" : "write"));
ssl_GetSSL3HandshakeLock(ss);
rv = tls13_SendKeyUpdate(ss, (dir == CipherSpecRead) ? update_requested : update_not_requested,
dir == CipherSpecWrite /* buffer */);
rv = tls13_SendKeyUpdate(ss, (dir == ssl_secret_read) ? update_requested : update_not_requested,
dir == ssl_secret_write /* buffer */);
ssl_ReleaseSSL3HandshakeLock(ss);
return rv;
}
@ -829,7 +802,7 @@ ssl_SecureRecv(sslSocket *ss, unsigned char *buf, int len, int flags)
}
ssl_Release1stHandshakeLock(ss);
} else {
if (tls13_CheckKeyUpdate(ss, CipherSpecRead) != SECSuccess) {
if (tls13_CheckKeyUpdate(ss, ssl_secret_read) != SECSuccess) {
rv = PR_FAILURE;
}
}
@ -955,7 +928,7 @@ ssl_SecureSend(sslSocket *ss, const unsigned char *buf, int len, int flags)
}
if (ss->firstHsDone) {
if (tls13_CheckKeyUpdate(ss, CipherSpecWrite) != SECSuccess) {
if (tls13_CheckKeyUpdate(ss, ssl_secret_write) != SECSuccess) {
rv = PR_FAILURE;
goto done;
}
@ -1010,6 +983,35 @@ ssl_SecureWrite(sslSocket *ss, const unsigned char *buf, int len)
return ssl_SecureSend(ss, buf, len, 0);
}
SECStatus
SSLExp_RecordLayerWriteCallback(PRFileDesc *fd, SSLRecordWriteCallback cb,
void *arg)
{
sslSocket *ss = ssl_FindSocket(fd);
if (!ss) {
SSL_DBG(("%d: SSL[%d]: invalid socket for SSL_RecordLayerWriteCallback",
SSL_GETPID(), fd));
return SECFailure;
}
if (IS_DTLS(ss)) {
SSL_DBG(("%d: SSL[%d]: DTLS socket for SSL_RecordLayerWriteCallback",
SSL_GETPID(), fd));
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
/* This needs both HS and Xmit locks because this value is checked under
* both locks. HS to disable reading from the underlying IO layer; Xmit to
* prevent writing. */
ssl_GetSSL3HandshakeLock(ss);
ssl_GetXmitBufLock(ss);
ss->recordWriteCallback = cb;
ss->recordWriteCallbackArg = arg;
ssl_ReleaseXmitBufLock(ss);
ssl_ReleaseSSL3HandshakeLock(ss);
return SECSuccess;
}
SECStatus
SSL_AlertReceivedCallback(PRFileDesc *fd, SSLAlertCallback cb, void *arg)
{

View file

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

View file

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

View file

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

View file

@ -43,6 +43,11 @@ typedef enum {
ssl_ct_ack = 25
} SSLContentType;
typedef enum {
ssl_secret_read = 1,
ssl_secret_write = 2,
} SSLSecretDirection;
typedef struct SSL3StatisticsStr {
/* statistics from ssl3_SendClientHello (sch) */
long sch_sid_cache_hits;
@ -328,6 +333,9 @@ typedef struct SSLChannelInfoStr {
/* Preliminary channel info */
#define ssl_preinfo_version (1U << 0)
#define ssl_preinfo_cipher_suite (1U << 1)
#define ssl_preinfo_0rtt_cipher_suite (1U << 2)
/* ssl_preinfo_all doesn't contain ssl_preinfo_0rtt_cipher_suite because that
* field is only set if 0-RTT is sent (client) or accepted (server). */
#define ssl_preinfo_all (ssl_preinfo_version | ssl_preinfo_cipher_suite)
typedef struct SSLPreliminaryChannelInfoStr {
@ -359,6 +367,13 @@ typedef struct SSLPreliminaryChannelInfoStr {
* resume this session. */
PRUint32 maxEarlyDataSize;
/* The following fields were added in NSS 3.39. */
/* This reports the cipher suite used for 0-RTT if it sent or accepted. For
* a client, this is set earlier than |cipherSuite|, and will match that
* value if 0-RTT is accepted by the server. The server only sets this
* after accepting 0-RTT, so this will contain the same value. */
PRUint16 zeroRttCipherSuite;
/* When adding new fields to this structure, please document the
* NSS version in which they were added. */
} SSLPreliminaryChannelInfo;
@ -407,6 +422,12 @@ typedef struct SSLCipherSuiteInfoStr {
* this instead of |authAlgorithm|. */
SSLAuthType authType;
/* The following fields were added in NSS 3.39. */
/* This reports the hash function used in the TLS KDF, or HKDF for TLS 1.3.
* For suites defined for versions of TLS earlier than TLS 1.2, this reports
* ssl_hash_none. */
SSLHashType kdfHash;
/* When adding new fields to this structure, please document the
* NSS version in which they were added. */
} SSLCipherSuiteInfo;
@ -450,6 +471,7 @@ typedef enum {
ssl_tls13_psk_key_exchange_modes_xtn = 45,
ssl_tls13_ticket_early_data_info_xtn = 46, /* Deprecated. */
ssl_tls13_certificate_authorities_xtn = 47,
ssl_tls13_post_handshake_auth_xtn = 49,
ssl_signature_algorithms_cert_xtn = 50,
ssl_tls13_key_share_xtn = 51,
ssl_next_proto_nego_xtn = 13172, /* Deprecated. */

View file

@ -26,7 +26,7 @@
#include "tls13hashstate.h"
static SECStatus tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch,
CipherSpecDirection install,
SSLSecretDirection install,
PRBool deleteSecret);
static SECStatus tls13_AESGCM(
ssl3KeyMaterial *keys,
@ -56,6 +56,7 @@ static SECStatus tls13_SendCertificate(sslSocket *ss);
static SECStatus tls13_HandleCertificate(
sslSocket *ss, PRUint8 *b, PRUint32 length);
static SECStatus tls13_ReinjectHandshakeTranscript(sslSocket *ss);
static SECStatus tls13_SendCertificateRequest(sslSocket *ss);
static SECStatus tls13_HandleCertificateRequest(sslSocket *ss, PRUint8 *b,
PRUint32 length);
static SECStatus
@ -104,6 +105,9 @@ static SECStatus tls13_ComputeFinished(
PRBool sending, PRUint8 *output, unsigned int *outputLen,
unsigned int maxOutputLen);
static SECStatus tls13_SendClientSecondRound(sslSocket *ss);
static SECStatus tls13_SendClientSecondFlight(sslSocket *ss,
PRBool sendClientCert,
SSL3AlertDescription *sendAlert);
static SECStatus tls13_FinishHandshake(sslSocket *ss);
const char kHkdfLabelClient[] = "c";
@ -588,13 +592,13 @@ loser:
}
static PRBool
tls13_UseServerSecret(sslSocket *ss, CipherSpecDirection direction)
tls13_UseServerSecret(sslSocket *ss, SSLSecretDirection direction)
{
return ss->sec.isServer == (direction == CipherSpecWrite);
return ss->sec.isServer == (direction == ssl_secret_write);
}
static PK11SymKey **
tls13_TrafficSecretRef(sslSocket *ss, CipherSpecDirection direction)
tls13_TrafficSecretRef(sslSocket *ss, SSLSecretDirection direction)
{
if (tls13_UseServerSecret(ss, direction)) {
return &ss->ssl3.hs.serverTrafficSecret;
@ -603,7 +607,7 @@ tls13_TrafficSecretRef(sslSocket *ss, CipherSpecDirection direction)
}
SECStatus
tls13_UpdateTrafficKeys(sslSocket *ss, CipherSpecDirection direction)
tls13_UpdateTrafficKeys(sslSocket *ss, SSLSecretDirection direction)
{
PK11SymKey **secret;
PK11SymKey *updatedSecret;
@ -626,7 +630,7 @@ tls13_UpdateTrafficKeys(sslSocket *ss, CipherSpecDirection direction)
*secret = updatedSecret;
ssl_GetSpecReadLock(ss);
if (direction == CipherSpecRead) {
if (direction == ssl_secret_read) {
epoch = ss->ssl3.crSpec->epoch;
} else {
epoch = ss->ssl3.cwSpec->epoch;
@ -640,6 +644,11 @@ tls13_UpdateTrafficKeys(sslSocket *ss, CipherSpecDirection direction)
}
++epoch;
if (ss->secretCallback) {
ss->secretCallback(ss->fd, epoch, direction, updatedSecret,
ss->secretCallbackArg);
}
rv = tls13_SetCipherSpec(ss, epoch, direction, PR_FALSE);
if (rv != SECSuccess) {
FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
@ -698,7 +707,7 @@ tls13_SendKeyUpdate(sslSocket *ss, tls13KeyUpdateRequest request, PRBool buffer)
}
ssl_ReleaseXmitBufLock(ss);
rv = tls13_UpdateTrafficKeys(ss, CipherSpecWrite);
rv = tls13_UpdateTrafficKeys(ss, ssl_secret_write);
if (rv != SECSuccess) {
goto loser; /* error code set by tls13_UpdateTrafficKeys */
}
@ -791,7 +800,7 @@ tls13_HandleKeyUpdate(sslSocket *ss, PRUint8 *b, unsigned int length)
return SECFailure;
}
rv = tls13_UpdateTrafficKeys(ss, CipherSpecRead);
rv = tls13_UpdateTrafficKeys(ss, ssl_secret_read);
if (rv != SECSuccess) {
return SECFailure; /* Error code set by tls13_UpdateTrafficKeys. */
}
@ -820,6 +829,56 @@ tls13_HandleKeyUpdate(sslSocket *ss, PRUint8 *b, unsigned int length)
return SECSuccess;
}
SECStatus
SSLExp_SendCertificateRequest(PRFileDesc *fd)
{
SECStatus rv;
sslSocket *ss = ssl_FindSocket(fd);
if (!ss) {
return SECFailure;
}
/* Not supported. */
if (IS_DTLS(ss)) {
PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_VERSION);
return SECFailure;
}
if (!ss->firstHsDone || ss->version < SSL_LIBRARY_VERSION_TLS_1_3) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
if (ss->ssl3.clientCertRequested) {
PORT_SetError(PR_WOULD_BLOCK_ERROR);
return SECFailure;
}
rv = TLS13_CHECK_HS_STATE(ss, SEC_ERROR_INVALID_ARGS,
idle_handshake);
if (rv != SECSuccess) {
return SECFailure;
}
if (!ssl3_ExtensionNegotiated(ss, ssl_tls13_post_handshake_auth_xtn)) {
PORT_SetError(SSL_ERROR_MISSING_POST_HANDSHAKE_AUTH_EXTENSION);
return SECFailure;
}
ssl_GetSSL3HandshakeLock(ss);
rv = tls13_SendCertificateRequest(ss);
if (rv == SECSuccess) {
ssl_GetXmitBufLock(ss);
rv = ssl3_FlushHandshake(ss, 0);
ssl_ReleaseXmitBufLock(ss);
ss->ssl3.clientCertRequested = PR_TRUE;
}
ssl_ReleaseSSL3HandshakeLock(ss);
return rv;
}
SECStatus
tls13_HandlePostHelloHandshakeMessage(sslSocket *ss, PRUint8 *b, PRUint32 length)
{
@ -1030,6 +1089,13 @@ tls13_DeriveEarlySecrets(sslSocket *ss)
return SECFailure;
}
if (ss->secretCallback) {
ss->secretCallback(ss->fd, (PRUint16)TrafficKeyEarlyApplicationData,
ss->sec.isServer ? ssl_secret_read : ssl_secret_write,
ss->ssl3.hs.clientEarlyTrafficSecret,
ss->secretCallbackArg);
}
rv = tls13_DeriveSecretWrap(ss, ss->ssl3.hs.currentSecret,
NULL, kHkdfLabelEarlyExporterSecret,
keylogLabelEarlyExporterSecret,
@ -1098,6 +1164,18 @@ tls13_ComputeHandshakeSecrets(sslSocket *ss)
return rv;
}
if (ss->secretCallback) {
SSLSecretDirection dir =
ss->sec.isServer ? ssl_secret_read : ssl_secret_write;
ss->secretCallback(ss->fd, (PRUint16)TrafficKeyHandshake, dir,
ss->ssl3.hs.clientHsTrafficSecret,
ss->secretCallbackArg);
dir = ss->sec.isServer ? ssl_secret_write : ssl_secret_read;
ss->secretCallback(ss->fd, (PRUint16)TrafficKeyHandshake, dir,
ss->ssl3.hs.serverHsTrafficSecret,
ss->secretCallbackArg);
}
SSL_TRC(5, ("%d: TLS13[%d]: compute master secret (%s)",
SSL_GETPID(), ss->fd, SSL_ROLE(ss)));
@ -1148,6 +1226,18 @@ tls13_ComputeApplicationSecrets(sslSocket *ss)
return SECFailure;
}
if (ss->secretCallback) {
SSLSecretDirection dir =
ss->sec.isServer ? ssl_secret_read : ssl_secret_write;
ss->secretCallback(ss->fd, (PRUint16)TrafficKeyApplicationData,
dir, ss->ssl3.hs.clientTrafficSecret,
ss->secretCallbackArg);
dir = ss->sec.isServer ? ssl_secret_write : ssl_secret_read;
ss->secretCallback(ss->fd, (PRUint16)TrafficKeyApplicationData,
dir, ss->ssl3.hs.serverTrafficSecret,
ss->secretCallbackArg);
}
rv = tls13_DeriveSecretWrap(ss, ss->ssl3.hs.currentSecret,
NULL, kHkdfLabelExporterMasterSecret,
keylogLabelExporterSecret,
@ -1294,6 +1384,8 @@ tls13_NegotiateZeroRtt(sslSocket *ss, const sslSessionID *sid)
PORT_Assert(ss->statelessResume);
ss->ssl3.hs.zeroRttState = ssl_0rtt_accepted;
ss->ssl3.hs.zeroRttIgnore = ssl_0rtt_ignore_none;
ss->ssl3.hs.zeroRttSuite = ss->ssl3.hs.cipher_suite;
ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_0rtt_cipher_suite;
}
/* Check if the offered group is acceptable. */
@ -2101,8 +2193,27 @@ tls13_SendCertificateRequest(sslSocket *ss)
/* We should always have at least one of these. */
PORT_Assert(SSL_BUFFER_LEN(&extensionBuf) > 0);
/* Create a new request context for post-handshake authentication */
if (ss->firstHsDone) {
PRUint8 context[16];
SECItem contextItem = { siBuffer, context, sizeof(context) };
rv = PK11_GenerateRandom(context, sizeof(context));
if (rv != SECSuccess) {
goto loser;
}
SECITEM_FreeItem(&ss->xtnData.certReqContext, PR_FALSE);
rv = SECITEM_CopyItem(NULL, &ss->xtnData.certReqContext, &contextItem);
if (rv != SECSuccess) {
FATAL_ERROR(ss, SEC_ERROR_NO_MEMORY, internal_error);
goto loser;
}
}
rv = ssl3_AppendHandshakeHeader(ss, ssl_hs_certificate_request,
1 + 0 + /* empty request context */
1 + /* request context length */
ss->xtnData.certReqContext.len +
2 + /* extension length */
SSL_BUFFER_LEN(&extensionBuf));
if (rv != SECSuccess) {
@ -2110,7 +2221,8 @@ tls13_SendCertificateRequest(sslSocket *ss)
}
/* Context. */
rv = ssl3_AppendHandshakeNumber(ss, 0, 1);
rv = ssl3_AppendHandshakeVariable(ss, ss->xtnData.certReqContext.data,
ss->xtnData.certReqContext.len, 1);
if (rv != SECSuccess) {
goto loser; /* err set by AppendHandshake. */
}
@ -2198,7 +2310,7 @@ tls13_HandleHelloRetryRequest(sslSocket *ss, const PRUint8 *savedMsg,
/* Restore the null cipher spec for writing. */
ssl_GetSpecWriteLock(ss);
ssl_CipherSpecRelease(ss->ssl3.cwSpec);
ss->ssl3.cwSpec = ssl_FindCipherSpecByEpoch(ss, CipherSpecWrite,
ss->ssl3.cwSpec = ssl_FindCipherSpecByEpoch(ss, ssl_secret_write,
TrafficKeyClearText);
PORT_Assert(ss->ssl3.cwSpec);
ssl_ReleaseSpecWriteLock(ss);
@ -2274,25 +2386,49 @@ tls13_HandleCertificateRequest(sslSocket *ss, PRUint8 *b, PRUint32 length)
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
/* Client */
rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST,
wait_cert_request);
if (ss->opt.enablePostHandshakeAuth) {
rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST,
wait_cert_request, idle_handshake);
} else {
rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST,
wait_cert_request);
}
if (rv != SECSuccess) {
return SECFailure;
}
PORT_Assert(ss->ssl3.clientCertChain == NULL);
PORT_Assert(ss->ssl3.clientCertificate == NULL);
PORT_Assert(ss->ssl3.clientPrivateKey == NULL);
PORT_Assert(!ss->ssl3.hs.clientCertRequested);
if (ss->firstHsDone) {
/* clean up anything left from previous handshake. */
if (ss->ssl3.clientCertChain != NULL) {
CERT_DestroyCertificateList(ss->ssl3.clientCertChain);
ss->ssl3.clientCertChain = NULL;
}
if (ss->ssl3.clientCertificate != NULL) {
CERT_DestroyCertificate(ss->ssl3.clientCertificate);
ss->ssl3.clientCertificate = NULL;
}
if (ss->ssl3.clientPrivateKey != NULL) {
SECKEY_DestroyPrivateKey(ss->ssl3.clientPrivateKey);
ss->ssl3.clientPrivateKey = NULL;
}
SECITEM_FreeItem(&ss->xtnData.certReqContext, PR_FALSE);
ss->xtnData.certReqContext.data = NULL;
} else {
PORT_Assert(ss->ssl3.clientCertChain == NULL);
PORT_Assert(ss->ssl3.clientCertificate == NULL);
PORT_Assert(ss->ssl3.clientPrivateKey == NULL);
PORT_Assert(!ss->ssl3.hs.clientCertRequested);
PORT_Assert(ss->xtnData.certReqContext.data == NULL);
}
rv = ssl3_ConsumeHandshakeVariable(ss, &context, 1, &b, &length);
if (rv != SECSuccess) {
return SECFailure;
}
/* We don't support post-handshake client auth, the certificate request
* context must always be empty. */
if (context.len > 0) {
/* Unless it is a post-handshake client auth, the certificate
* request context must be empty. */
if (!ss->firstHsDone && context.len > 0) {
FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CERT_REQUEST, illegal_parameter);
return SECFailure;
}
@ -2326,7 +2462,35 @@ tls13_HandleCertificateRequest(sslSocket *ss, PRUint8 *b, PRUint32 length)
}
ss->ssl3.hs.clientCertRequested = PR_TRUE;
TLS13_SET_HS_STATE(ss, wait_server_cert);
if (ss->firstHsDone) {
SSL3AlertDescription sendAlert = no_alert;
/* Request a client certificate. */
rv = ssl3_CompleteHandleCertificateRequest(
ss, ss->xtnData.sigSchemes, ss->xtnData.numSigSchemes,
&ss->xtnData.certReqAuthorities);
if (rv != SECSuccess) {
FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
return rv;
}
ssl_GetXmitBufLock(ss);
rv = tls13_SendClientSecondFlight(ss, !ss->ssl3.sendEmptyCert,
&sendAlert);
ssl_ReleaseXmitBufLock(ss);
if (rv != SECSuccess) {
if (sendAlert != no_alert) {
FATAL_ERROR(ss, PORT_GetError(), sendAlert);
} else {
LOG_ERROR(ss, PORT_GetError());
}
return SECFailure;
}
PORT_Assert(ss->ssl3.hs.ws == idle_handshake);
} else {
TLS13_SET_HS_STATE(ss, wait_server_cert);
}
return SECSuccess;
}
@ -2348,7 +2512,7 @@ tls13_SendEncryptedServerSequence(sslSocket *ss)
}
rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake,
CipherSpecWrite, PR_FALSE);
ssl_secret_write, PR_FALSE);
if (rv != SECSuccess) {
LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE);
return SECFailure;
@ -2458,7 +2622,7 @@ tls13_SendServerHelloSequence(sslSocket *ss)
}
rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData,
CipherSpecWrite, PR_FALSE);
ssl_secret_write, PR_FALSE);
if (rv != SECSuccess) {
LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE);
return SECFailure;
@ -2470,7 +2634,7 @@ tls13_SendServerHelloSequence(sslSocket *ss)
}
if (ss->ssl3.hs.zeroRttState == ssl_0rtt_accepted) {
rv = tls13_SetCipherSpec(ss, TrafficKeyEarlyApplicationData,
CipherSpecRead, PR_TRUE);
ssl_secret_read, PR_TRUE);
if (rv != SECSuccess) {
LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE);
return SECFailure;
@ -2482,7 +2646,7 @@ tls13_SendServerHelloSequence(sslSocket *ss)
rv = tls13_SetCipherSpec(ss,
TrafficKeyHandshake,
CipherSpecRead, PR_FALSE);
ssl_secret_read, PR_FALSE);
if (rv != SECSuccess) {
LOG_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE);
return SECFailure;
@ -2591,11 +2755,11 @@ tls13_HandleServerHelloPart2(sslSocket *ss)
/* When we send 0-RTT, we saved the null spec in case we needed it to
* send another ClientHello in response to a HelloRetryRequest. Now
* that we won't be receiving a HelloRetryRequest, release the spec. */
ssl_CipherSpecReleaseByEpoch(ss, CipherSpecWrite, TrafficKeyClearText);
ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_write, TrafficKeyClearText);
}
rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake,
CipherSpecRead, PR_FALSE);
ssl_secret_read, PR_FALSE);
if (rv != SECSuccess) {
FATAL_ERROR(ss, SSL_ERROR_INIT_CIPHER_SUITE_FAILURE, internal_error);
return SECFailure;
@ -2862,8 +3026,13 @@ tls13_HandleCertificate(sslSocket *ss, PRUint8 *b, PRUint32 length)
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
if (ss->sec.isServer) {
rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE,
wait_client_cert);
if (ss->ssl3.clientCertRequested) {
rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE,
idle_handshake);
} else {
rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE,
wait_client_cert);
}
} else {
rv = TLS13_CHECK_HS_STATE(ss, SSL_ERROR_RX_UNEXPECTED_CERTIFICATE,
wait_cert_request, wait_server_cert);
@ -2873,7 +3042,7 @@ tls13_HandleCertificate(sslSocket *ss, PRUint8 *b, PRUint32 length)
/* We can ignore any other cleartext from the client. */
if (ss->sec.isServer && IS_DTLS(ss)) {
ssl_CipherSpecReleaseByEpoch(ss, CipherSpecRead, TrafficKeyClearText);
ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read, TrafficKeyClearText);
dtls_ReceivedFirstMessageInFlight(ss);
}
/* Process the context string */
@ -2881,10 +3050,12 @@ tls13_HandleCertificate(sslSocket *ss, PRUint8 *b, PRUint32 length)
if (rv != SECSuccess)
return SECFailure;
if (context.len) {
/* The context string MUST be empty */
FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CERTIFICATE, illegal_parameter);
return SECFailure;
if (ss->ssl3.clientCertRequested) {
PORT_Assert(ss->sec.isServer);
if (SECITEM_CompareItem(&context, &ss->xtnData.certReqContext) != 0) {
FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CERTIFICATE, illegal_parameter);
return SECFailure;
}
}
rv = ssl3_ConsumeHandshakeVariable(ss, &certList, 3, &b, &length);
@ -3125,6 +3296,25 @@ tls13_DeriveSecretWrap(sslSocket *ss, PK11SymKey *key,
return SECSuccess;
}
SECStatus
SSLExp_SecretCallback(PRFileDesc *fd, SSLSecretCallback cb, void *arg)
{
sslSocket *ss = ssl_FindSocket(fd);
if (!ss) {
SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SecretCallback",
SSL_GETPID(), fd));
return SECFailure;
}
ssl_Get1stHandshakeLock(ss);
ssl_GetSSL3HandshakeLock(ss);
ss->secretCallback = cb;
ss->secretCallbackArg = arg;
ssl_ReleaseSSL3HandshakeLock(ss);
ssl_Release1stHandshakeLock(ss);
return SECSuccess;
}
/* Derive traffic keys for the next cipher spec in the queue. */
static SECStatus
tls13_DeriveTrafficKeys(sslSocket *ss, ssl3CipherSpec *spec,
@ -3251,7 +3441,7 @@ tls13_SetupPendingCipherSpec(sslSocket *ss, ssl3CipherSpec *spec)
/* We want to keep read cipher specs around longer because
* there are cases where we might get either epoch N or
* epoch N+1. */
if (IS_DTLS(ss) && spec->direction == CipherSpecRead) {
if (IS_DTLS(ss) && spec->direction == ssl_secret_read) {
ssl_CipherSpecAddRef(spec);
}
@ -3274,7 +3464,7 @@ tls13_SetupPendingCipherSpec(sslSocket *ss, ssl3CipherSpec *spec)
/* The record size limit is reduced by one so that the remainder of the
* record handling code can use the same checks for all versions. */
if (ssl3_ExtensionNegotiated(ss, ssl_record_size_limit_xtn)) {
spec->recordSizeLimit = ((spec->direction == CipherSpecRead)
spec->recordSizeLimit = ((spec->direction == ssl_secret_read)
? ss->opt.recordSizeLimit
: ss->xtnData.recordSizeLimit) -
1;
@ -3310,7 +3500,7 @@ tls13_SetAlertCipherSpec(sslSocket *ss)
}
rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake,
CipherSpecWrite, PR_FALSE);
ssl_secret_write, PR_FALSE);
if (rv != SECSuccess) {
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
return SECFailure;
@ -3325,7 +3515,7 @@ tls13_SetAlertCipherSpec(sslSocket *ss)
*/
static SECStatus
tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch,
CipherSpecDirection direction, PRBool deleteSecret)
SSLSecretDirection direction, PRBool deleteSecret)
{
TrafficKeyType type;
SECStatus rv;
@ -3364,7 +3554,7 @@ tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch,
}
/* Now that we've set almost everything up, finally cut over. */
specp = (direction == CipherSpecRead) ? &ss->ssl3.crSpec : &ss->ssl3.cwSpec;
specp = (direction == ssl_secret_read) ? &ss->ssl3.crSpec : &ss->ssl3.cwSpec;
ssl_GetSpecWriteLock(ss);
ssl_CipherSpecRelease(*specp); /* May delete old cipher. */
*specp = spec; /* Overwrite. */
@ -3373,11 +3563,6 @@ tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch,
SSL_TRC(3, ("%d: TLS13[%d]: %s installed key for epoch=%d (%s) dir=%s",
SSL_GETPID(), ss->fd, SSL_ROLE(ss), spec->epoch,
spec->phase, SPEC_DIR(spec)));
if (ss->ssl3.changedCipherSpecFunc) {
ss->ssl3.changedCipherSpecFunc(ss->ssl3.changedCipherSpecArg,
direction == CipherSpecWrite, spec);
}
return SECSuccess;
loser:
@ -3937,6 +4122,10 @@ tls13_HandleCertificateVerify(sslSocket *ss, PRUint8 *b, PRUint32 length)
}
}
if (ss->ssl3.clientCertRequested) {
PORT_Assert(ss->sec.isServer);
ss->ssl3.clientCertRequested = PR_FALSE;
}
TLS13_SET_HS_STATE(ss, wait_finished);
return SECSuccess;
@ -4238,26 +4427,32 @@ tls13_ServerHandleFinished(sslSocket *ss, PRUint8 *b, PRUint32 length)
SSL_TRC(3, ("%d: TLS13[%d]: server handle finished handshake",
SSL_GETPID(), ss->fd));
rv = tls13_CommonHandleFinished(ss, ss->ssl3.hs.clientHsTrafficSecret,
rv = tls13_CommonHandleFinished(ss,
ss->firstHsDone ? ss->ssl3.hs.clientTrafficSecret : ss->ssl3.hs.clientHsTrafficSecret,
b, length);
if (rv != SECSuccess) {
return SECFailure;
}
if (ss->firstHsDone) {
TLS13_SET_HS_STATE(ss, idle_handshake);
return SECSuccess;
}
if (!tls13_ShouldRequestClientAuth(ss) &&
(ss->ssl3.hs.zeroRttState != ssl_0rtt_done)) {
dtls_ReceivedFirstMessageInFlight(ss);
}
rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData,
CipherSpecRead, PR_FALSE);
ssl_secret_read, PR_FALSE);
if (rv != SECSuccess) {
FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
return SECFailure;
}
if (IS_DTLS(ss)) {
ssl_CipherSpecReleaseByEpoch(ss, CipherSpecRead, TrafficKeyClearText);
ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read, TrafficKeyClearText);
/* We need to keep the handshake cipher spec so we can
* read re-transmitted client Finished. */
rv = dtls_StartTimer(ss, ss->ssl3.hs.hdTimer,
@ -4361,7 +4556,7 @@ tls13_SendClientSecondFlight(sslSocket *ss, PRBool sendClientCert,
}
}
rv = tls13_SendFinished(ss, ss->ssl3.hs.clientHsTrafficSecret);
rv = tls13_SendFinished(ss, ss->firstHsDone ? ss->ssl3.hs.clientTrafficSecret : ss->ssl3.hs.clientHsTrafficSecret);
if (rv != SECSuccess) {
return SECFailure; /* err code was set. */
}
@ -4404,7 +4599,8 @@ tls13_SendClientSecondRound(sslSocket *ss)
" certificate authentication is still pending.",
SSL_GETPID(), ss->fd));
ss->ssl3.hs.restartTarget = tls13_SendClientSecondRound;
return SECWouldBlock;
PORT_SetError(PR_WOULD_BLOCK_ERROR);
return SECFailure;
}
rv = tls13_ComputeApplicationSecrets(ss);
@ -4432,14 +4628,14 @@ tls13_SendClientSecondRound(sslSocket *ss)
}
rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake,
CipherSpecWrite, PR_FALSE);
ssl_secret_write, PR_FALSE);
if (rv != SECSuccess) {
FATAL_ERROR(ss, SSL_ERROR_INIT_CIPHER_SUITE_FAILURE, internal_error);
return SECFailure;
}
rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData,
CipherSpecRead, PR_FALSE);
ssl_secret_read, PR_FALSE);
if (rv != SECSuccess) {
FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
return SECFailure;
@ -4457,7 +4653,7 @@ tls13_SendClientSecondRound(sslSocket *ss)
return SECFailure;
}
rv = tls13_SetCipherSpec(ss, TrafficKeyApplicationData,
CipherSpecWrite, PR_FALSE);
ssl_secret_write, PR_FALSE);
if (rv != SECSuccess) {
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
return SECFailure;
@ -4815,7 +5011,8 @@ static const struct {
{ ssl_tls13_supported_versions_xtn, _M3(client_hello, server_hello,
hello_retry_request) },
{ ssl_record_size_limit_xtn, _M2(client_hello, encrypted_extensions) },
{ ssl_tls13_encrypted_sni_xtn, _M2(client_hello, encrypted_extensions) }
{ ssl_tls13_encrypted_sni_xtn, _M2(client_hello, encrypted_extensions) },
{ ssl_tls13_post_handshake_auth_xtn, _M1(client_hello) }
};
tls13ExtensionStatus
@ -4924,7 +5121,7 @@ tls13_ProtectRecord(sslSocket *ss,
const int tagLen = cipher_def->tag_size;
SECStatus rv;
PORT_Assert(cwSpec->direction == CipherSpecWrite);
PORT_Assert(cwSpec->direction == ssl_secret_write);
SSL_TRC(3, ("%d: TLS13[%d]: spec=%d epoch=%d (%s) protect 0x%0llx len=%u",
SSL_GETPID(), ss->fd, cwSpec, cwSpec->epoch, cwSpec->phase,
cwSpec->nextSeqNum, contentLen));
@ -5018,7 +5215,7 @@ tls13_UnprotectRecord(sslSocket *ss,
*alert = bad_record_mac; /* Default alert for most issues. */
PORT_Assert(spec->direction == CipherSpecRead);
PORT_Assert(spec->direction == ssl_secret_read);
SSL_TRC(3, ("%d: TLS13[%d]: spec=%d epoch=%d (%s) unprotect 0x%0llx len=%u",
SSL_GETPID(), ss->fd, spec, spec->epoch, spec->phase,
cText->seqNum, cText->buf->len));
@ -5175,6 +5372,9 @@ tls13_MaybeDo0RTTHandshake(sslSocket *ss)
ss->ssl3.hs.zeroRttState = ssl_0rtt_sent;
ss->ssl3.hs.zeroRttSuite = ss->ssl3.hs.cipher_suite;
/* Note: Reset the preliminary info here rather than just add 0-RTT. We are
* only guessing what might happen at this point.*/
ss->ssl3.hs.preliminaryInfo = ssl_preinfo_0rtt_cipher_suite;
SSL_TRC(3, ("%d: TLS13[%d]: in 0-RTT mode", SSL_GETPID(), ss->fd));
@ -5203,9 +5403,6 @@ tls13_MaybeDo0RTTHandshake(sslSocket *ss)
}
}
/* Cipher suite already set in tls13_SetupClientHello. */
ss->ssl3.hs.preliminaryInfo = 0;
rv = tls13_DeriveEarlySecrets(ss);
if (rv != SECSuccess) {
return SECFailure;
@ -5216,7 +5413,7 @@ tls13_MaybeDo0RTTHandshake(sslSocket *ss)
ssl_CipherSpecAddRef(ss->ssl3.cwSpec);
rv = tls13_SetCipherSpec(ss, TrafficKeyEarlyApplicationData,
CipherSpecWrite, PR_TRUE);
ssl_secret_write, PR_TRUE);
if (rv != SECSuccess) {
return SECFailure;
}
@ -5279,7 +5476,7 @@ tls13_HandleEndOfEarlyData(sslSocket *ss, PRUint8 *b, PRUint32 length)
/* We shouldn't be getting any more early data, and if we do,
* it is because of reordering and we drop it. */
if (IS_DTLS(ss)) {
ssl_CipherSpecReleaseByEpoch(ss, CipherSpecRead,
ssl_CipherSpecReleaseByEpoch(ss, ssl_secret_read,
TrafficKeyEarlyApplicationData);
dtls_ReceivedFirstMessageInFlight(ss);
}
@ -5292,7 +5489,7 @@ tls13_HandleEndOfEarlyData(sslSocket *ss, PRUint8 *b, PRUint32 length)
}
rv = tls13_SetCipherSpec(ss, TrafficKeyHandshake,
CipherSpecRead, PR_FALSE);
ssl_secret_read, PR_FALSE);
if (rv != SECSuccess) {
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
return SECFailure;

View file

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

View file

@ -915,6 +915,37 @@ tls13_ServerHandleCookieXtn(const sslSocket *ss, TLSExtensionData *xtnData,
return SECSuccess;
}
SECStatus
tls13_ClientSendPostHandshakeAuthXtn(const sslSocket *ss,
TLSExtensionData *xtnData,
sslBuffer *buf, PRBool *added)
{
SSL_TRC(3, ("%d: TLS13[%d]: send post_handshake_auth extension",
SSL_GETPID(), ss->fd));
*added = ss->opt.enablePostHandshakeAuth;
return SECSuccess;
}
SECStatus
tls13_ServerHandlePostHandshakeAuthXtn(const sslSocket *ss,
TLSExtensionData *xtnData,
SECItem *data)
{
SSL_TRC(3, ("%d: TLS13[%d]: handle post_handshake_auth extension",
SSL_GETPID(), ss->fd));
if (data->len) {
PORT_SetError(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO);
return SECFailure;
}
/* Keep track of negotiated extensions. */
xtnData->negotiated[xtnData->numNegotiated++] = ssl_tls13_post_handshake_auth_xtn;
return SECSuccess;
}
/*
* enum { psk_ke(0), psk_dhe_ke(1), (255) } PskKeyExchangeMode;
*

View file

@ -93,5 +93,11 @@ SECStatus tls13_ClientSendEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData
SECStatus tls13_ServerHandleEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData,
SECItem *data);
SECStatus tls13_ClientCheckEsniXtn(sslSocket *ss);
SECStatus tls13_ClientSendPostHandshakeAuthXtn(const sslSocket *ss,
TLSExtensionData *xtnData,
sslBuffer *buf, PRBool *added);
SECStatus tls13_ServerHandlePostHandshakeAuthXtn(const sslSocket *ss,
TLSExtensionData *xtnData,
SECItem *data);
#endif

View file

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

View file

@ -252,7 +252,15 @@ dbtest_main()
else
html_passed "Nicknane conflict test-setting nickname conflict was correctly rejected"
fi
# import a token private key and make sure the corresponding public key is
# created
${BINDIR}/pk11importtest -d ${CONFLICT_DIR} -f ${R_PWFILE}
ret=$?
if [ $ret -ne 0 ]; then
html_failed "Importing Token Private Key does not create the corrresponding Public Key"
else
html_passed "Importing Token Private Key correctly creates the corrresponding Public Key"
fi
}
################## main #################################################

View file

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

View file

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

View file

@ -103,14 +103,14 @@ var resolveCallback = SpecialPowers.wrapCallbackObject({
// The proxyChannel needs to move to at least on-modify-request to
// have valid ProxyInfo, but we use OnStartRequest during startup()
// for simplicity.
proxyChannel = SpecialPowers.Services.io.newChannel2(proxiedHost,
null,
null,
null, // aLoadingNode
systemPrincipal,
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER);
proxyChannel = SpecialPowers.Services.io.newChannel(proxiedHost,
null,
null,
null, // aLoadingNode
systemPrincipal,
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER);
proxyChannel.asyncOpen(SpecialPowers.wrapCallbackObject(new proxyChannelListener()));
},
});
@ -119,14 +119,14 @@ function startup() {
// Need to allow for arbitrary network servers defined in PAC instead of a hardcoded moz-proxy.
var pps = SpecialPowers.Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
var channel = SpecialPowers.Services.io.newChannel2("http://example.com",
null,
null,
null, // aLoadingNode
systemPrincipal,
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER);
var channel = SpecialPowers.Services.io.newChannel("http://example.com",
null,
null,
null, // aLoadingNode
systemPrincipal,
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
Ci.nsIContentPolicy.TYPE_OTHER);
pps.asyncResolve(channel, 0, resolveCallback);
}

View file

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

View file

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