merge mozilla-inbound to mozilla-central

This commit is contained in:
Carsten "Tomcat" Book 2014-02-19 13:58:42 +01:00
commit f2a18fd9de
71 changed files with 1959 additions and 586 deletions

View File

@ -12,7 +12,3 @@ endif
ifdef MOZ_ENABLE_DBUS
CXXFLAGS += $(MOZ_DBUS_CFLAGS)
endif
ifneq ($(A11Y_LOG),0)
DEFINES += -DA11Y_LOG
endif

View File

@ -7,7 +7,3 @@ include $(topsrcdir)/config/rules.mk
ifdef MOZ_ENABLE_GTK
CXXFLAGS += $(MOZ_CAIRO_CFLAGS)
endif
ifneq ($(A11Y_LOG),0)
DEFINES += -DA11Y_LOG
endif

View File

@ -4,8 +4,6 @@
# 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('../shared.mozbuild')
EXPORTS += [
'AccEvent.h',
'nsAccessibilityService.h'
@ -53,7 +51,7 @@ UNIFIED_SOURCES += [
'TreeWalker.cpp',
]
if a11y_log:
if CONFIG['A11Y_LOG']:
UNIFIED_SOURCES += [
'Logging.cpp',
]

View File

@ -1,11 +0,0 @@
# 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/.
A11Y_LOG = 0
ifdef MOZ_DEBUG
A11Y_LOG = 1
endif
ifeq (,$(filter aurora beta release esr,$(MOZ_UPDATE_CHANNEL)))
A11Y_LOG = 1
endif

View File

@ -1,9 +0,0 @@
# 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 $(topsrcdir)/config/rules.mk
ifneq ($(A11Y_LOG),0)
DEFINES += -DA11Y_LOG
endif

View File

@ -1,10 +0,0 @@
#
# 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 $(topsrcdir)/config/rules.mk
ifneq ($(A11Y_LOG),0)
DEFINES += -DA11Y_LOG
endif

View File

@ -1,9 +0,0 @@
# 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 $(topsrcdir)/config/rules.mk
ifneq ($(A11Y_LOG),0)
DEFINES += -DA11Y_LOG
endif

View File

@ -1,9 +0,0 @@
# 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 $(topsrcdir)/config/rules.mk
ifneq ($(A11Y_LOG),0)
DEFINES += -DA11Y_LOG
endif

View File

@ -1,12 +0,0 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
a11y_log = 0
if CONFIG['MOZ_DEBUG']:
a11y_log = 1
if CONFIG['MOZ_UPDATE_CHANNEL'] not in ('aurora', 'beta', 'release', 'esr'):
a11y_log = 1

View File

@ -1,10 +0,0 @@
#
# 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 $(topsrcdir)/config/rules.mk
ifneq ($(A11Y_LOG),0)
DEFINES += -DA11Y_LOG
endif

View File

@ -7,7 +7,3 @@
OS_CXXFLAGS += -DNOMINMAX
include $(topsrcdir)/config/rules.mk
ifdef A11Y_LOG
DEFINES += -DA11Y_LOG
endif

View File

@ -1,9 +0,0 @@
# 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 $(topsrcdir)/config/rules.mk
ifneq ($(A11Y_LOG),0)
DEFINES += -DA11Y_LOG
endif

View File

@ -7,7 +7,3 @@
OS_CXXFLAGS += -DNOMINMAX
include $(topsrcdir)/config/rules.mk
ifdef A11Y_LOG
DEFINES += -DA11Y_LOG
endif

View File

@ -11,10 +11,6 @@ xpcaccevents_TARGET := export
include $(topsrcdir)/config/rules.mk
ifneq ($(A11Y_LOG),0)
DEFINES += -DA11Y_LOG
endif
xpcAccEvents.cpp: $(srcdir)/AccEvents.conf \
$(srcdir)/AccEventGen.py \
$(LIBXUL_DIST)/sdk/bin/header.py \

View File

@ -1,10 +0,0 @@
#
# 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 $(topsrcdir)/config/rules.mk
ifneq ($(A11Y_LOG),0)
DEFINES += -DA11Y_LOG
endif

View File

@ -0,0 +1,89 @@
.. _build_androideclipse:
========================
Android Eclipse Projects
========================
The build system contains alpha support for generating Android Eclipse
project files to aid with development.
To generate Android Eclipse project files, you'll need to have a fully
built and packaged tree::
mach build && mach package
(This is because Eclipse itself packages an APK containing
``omni.ja``, and ``omni.ja`` is only assembled during packaging.)
Then, simply generate the Android Eclipse build backend::
mach build-backend -b AndroidEclipse
If all goes well, the path to the generated projects should be
printed (currently, ``$OBJDIR/android_eclipse``).
To use the generated Android Eclipse project files, you'll need to
have a recent version of Eclipse (see `Tested Versions`_) with the
`Eclipse ADT plugin
<http://developer.android.com/tools/sdk/eclipse-adt.html>`_
installed. You can then import all the projects into Eclipse using
*File > Import ... > General > Existing Projects into Workspace*.
Updating Project Files
======================
As you pull and update the source tree, your Android Eclipse files may
fall out of sync with the build configuration. The tree should still
build fine from within Eclipse, but source files may be missing and in
rare circumstances Eclipse's index may not have the proper build
configuration.
To account for this, you'll want to periodically regenerate the
Android Eclipse project files. You can do this by running ``mach build
&& mach package && mach build-backend -b AndroidEclipse`` from the
command line. It's a good idea to refresh and clean build all projects
in Eclipse after doing this.
In future, we'd like to include an Android Eclipse run configuration
or build target that integrates updating the project files.
Currently, regeneration rewrites the original project files. **If
you've made any customizations to the projects, they will likely get
overwritten.** We would like to improve this user experience in the
future.
Troubleshooting
===============
If Eclipse's builder gets confused, you should always refresh and
clean build all projects. If Eclipse's builder is continually
confused, you can see a log of what is happening at
``$OBJDIR/android_eclipse/build.log``.
If you run into memory problems executing ``dex``, you should
`Increase Eclipse's memory limits <http://stackoverflow.com/a/11093228>`_.
The produced Android Eclipse project files are unfortunately not
portable. Please don't move them around.
Structure of Android Eclipse projects
=====================================
The Android Eclipse backend generates several projects spanning Fennec
itself and its tests. You'll mostly interact with the *Fennec* project
itself.
In future, we'd like to expand this documentation to include some of
the technical details of how the Eclipse integration works, and how to
add additional Android Eclipse projects using the ``moz.build``
system.
Tested Versions
===============
============ ==================================== =================
OS Version Working as of
============ ==================================== =================
Mac OS X Luna (Build id: 20130919-0819) February 2014
Mac OS X Kepler (Build id: 20131219-0014) February 2014
============ ==================================== =================

View File

@ -23,6 +23,7 @@ Important Concepts
preprocessor
jar-manifests
visualstudio
androideclipse
mozbuild
========

View File

@ -334,9 +334,9 @@ _ENABLE_PIC=1
# Determine if module being compiled is destined
# to be merged into libxul
ifeq ($(FINAL_LIBRARY),xul)
ifneq (,$(filter xul,$(FINAL_LIBRARY) $(LIBRARY_NAME)))
ifdef LIBXUL_LIBRARY
$(error FINAL_LIBRARY is "xul", LIBXUL_LIBRARY is implied)
$(error LIBRARY_NAME or FINAL_LIBRARY is "xul", LIBXUL_LIBRARY is implied)
endif
LIBXUL_LIBRARY := 1
endif

View File

@ -8618,6 +8618,11 @@ AC_SUBST(MOZ_FOLD_LIBS)
AC_SUBST(MOZ_ENABLE_SZIP)
AC_SUBST(MOZ_SZIP_FLAGS)
if test "$MOZ_DEBUG"; then
MOZ_EM_DEBUG=1
fi
AC_SUBST(MOZ_EM_DEBUG)
if test -n "$COMPILE_ENVIRONMENT"; then
AC_MSG_CHECKING([for posix_fallocate])
AC_TRY_LINK([#define _XOPEN_SOURCE 600
@ -8693,6 +8698,21 @@ if test "$ACCESSIBILITY" -a "$MOZ_ENABLE_GTK" ; then
AC_DEFINE_UNQUOTED(ATK_REV_VERSION, $ATK_REV_VERSION)
fi
if test "$MOZ_DEBUG"; then
A11Y_LOG=1
fi
case "$MOZ_UPDATE_CHANNEL" in
aurora|beta|release|esr)
;;
*)
A11Y_LOG=1
;;
esac
AC_SUBST(A11Y_LOG)
if test -n "$A11Y_LOG"; then
AC_DEFINE(A11Y_LOG)
fi
AC_SUBST(MOZILLA_VERSION)
AC_SUBST(ac_configure_args)

View File

@ -16,6 +16,15 @@ var tests = [
langTag: "de", inputWithGrouping: "123.456,78",
inputWithoutGrouping: "123456,78", value: 123456.78
},
// Extra german test to check that a locale that uses '.' as its grouping
// separator doesn't result in it being invalid (due to step mismatch) due
// to the de-localization code mishandling numbers that look like other
// numbers formatted for English speakers (i.e. treating this as 123.456
// instead of 123456):
{ desc: "German (test 2)",
langTag: "de", inputWithGrouping: "123.456",
inputWithoutGrouping: "123456", value: 123456
},
{ desc: "Hebrew",
langTag: "he", inputWithGrouping: "123,456.78",
inputWithoutGrouping: "123456.78", value: 123456.78

View File

@ -126,7 +126,7 @@ function runTest()
"42",
"-42", // should work for negative values
"42.1234",
"123.12345678912345", // double precision
"123.123456789123", // double precision
"1e2", // e should be usable
"2e1",
"1e-1", // value after e can be negative

View File

@ -45,14 +45,12 @@ function onSourceOutput(e) {
}
function disconnectSources() {
dump("disconnecting\n")
for (var i = 0; i < sourceCount; ++i) {
sources[i].disconnect();
}
SpecialPowers.forceGC();
SpecialPowers.forceCC();
dump("forced GC\n");
}
function startTest() {

View File

@ -3,7 +3,6 @@
<head>
<title>Test the OscillatorNode interface</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="webaudio.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
@ -28,25 +27,26 @@ addLoadEvent(function() {
}
}
sp.onaudioprocess = null;
dump("audioprocess received\n");
ok(isSilent, "OscillatorNode should be silent before calling start.");
SimpleTest.finish();
}
// Debug Logging for bug 966322
ok(true, "Load event ran");
dump("Load event ran\n");
var bs = context.createBufferSource();
bs.buffer = context.createBuffer(1, 1, context.sampleRate);
bs.start();
bs.onended = function() {
ok(true, "Graph is running");
dump("Graph is running\n");
}
});
// Debug Logging for bug 966322
ok(true, "script ran");
dump("script ran\n");
</script>
</pre>

View File

@ -60,7 +60,6 @@ var testProcessCount = 0;
function isChannelSilent(channel) {
for (var i = 0; i < channel.length; ++i) {
if (channel[i] != 0.0) {
dump("input at " + i + "\n");
return false;
}
}

View File

@ -1,7 +0,0 @@
# 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/.
ifdef MOZ_DEBUG
DEFINES += -DMOZ_DEBUG=1
endif

View File

@ -55,7 +55,7 @@ XPCOMUtils.defineLazyGetter(this, "libcutils", function() {
#endif
function debug(aMsg) {
#ifdef MOZ_DEBUG
#ifdef DEBUG
dump("-*- Webapps.jsm : " + aMsg + "\n");
#endif
}

View File

@ -9,9 +9,6 @@ endif
# Build MMX code either with VC or with gcc-on-x86
ifdef _MSC_VER
ifeq (86,$(findstring 86,$(OS_TEST)))
ifneq (64,$(findstring 64,$(OS_TEST)))
USE_MMX=1
endif
USE_SSE2=1
MMX_CFLAGS=
ifneq (,$(filter 1400 1500, $(_MSC_VER)))
@ -23,18 +20,13 @@ else
SSE2_CFLAGS=
endif
endif
ifeq (arm,$(findstring arm,$(OS_TEST)))
USE_ARM_SIMD_MSVC=1
endif
endif
ifdef GNU_CC
ifeq (ppc,$(findstring ppc,$(OS_TEST)))
USE_VMX=1
VMX_CFLAGS=-maltivec
endif
ifeq (86,$(findstring 86,$(OS_TEST)))
USE_MMX=1
MMX_CFLAGS=-mmmx -Winline
ifeq (64,$(findstring 64,$(OS_TEST)))
USE_SSE2=1
@ -53,42 +45,12 @@ ifeq (arm,$(findstring arm,$(OS_TEST)))
# This may be fixable if clang's ARM/iOS assembler improves into a
# viable solution in the future.
ifneq (Darwin,$(OS_ARCH))
ifdef HAVE_ARM_SIMD
USE_ARM_SIMD_GCC=1
endif
ifdef HAVE_ARM_NEON
USE_ARM_NEON_GCC=1
endif
endif
endif
endif
ifdef USE_MMX
CSRCS += pixman-mmx.c
DEFINES += -DUSE_MMX
endif
ifdef USE_SSE2
CSRCS += pixman-sse2.c
DEFINES += -DUSE_SSE -DUSE_SSE2
endif
ifdef USE_VMX
CSRCS += pixman-vmx.c
DEFINES += -DUSE_VMX
endif
ifdef USE_ARM_SIMD_GCC
CSRCS += pixman-arm-simd.c
DEFINES += -DUSE_ARM_SIMD
endif
ifdef USE_ARM_NEON_GCC
CSRCS += pixman-arm-neon.c
DEFINES += -DUSE_ARM_NEON
ARM_NEON_CFLAGS = -mfpu=neon
endif
endif
endif
endif
include $(topsrcdir)/config/rules.mk

View File

@ -75,3 +75,56 @@ if CONFIG['_MSC_VER']:
DEFINES['PACKAGE'] = 'mozpixman'
DEFINES['_USE_MATH_DEFINES'] = True
use_mmx = False
use_sse2 = False
use_vmx = False
use_arm_simd_gcc = False
use_arm_neon_gcc = False
if '86' in CONFIG['OS_TEST']:
if '64' in CONFIG['OS_TEST']:
if CONFIG['GNU_CC']:
use_sse2 = True
else:
if CONFIG['_MSC_VER']:
use_mmx = True
if CONFIG['HAVE_GCC_ALIGN_ARG_POINTER']:
use_sse2 = True
if CONFIG['GNU_CC']:
use_mmx = True
if CONFIG['_MSC_VER']:
use_sse2 = True
elif 'ppc' in CONFIG['OS_TEST']:
if CONFIG['GNU_CC']:
use_vmx = True
# Apple's arm assembler doesn't support the same syntax as
# the standard GNU assembler, so use the C fallback paths for now.
# This may be fixable if clang's ARM/iOS assembler improves into a
# viable solution in the future.
elif 'arm' in CONFIG['OS_TEST']:
if CONFIG['OS_ARCH'] != 'Darwin':
if CONFIG['HAVE_ARM_SIMD']:
use_arm_simd_gcc = True
if CONFIG['HAVE_ARM_NEON']:
use_arm_neon_gcc = True
if use_mmx:
DEFINES['USE_MMX'] = True
SOURCES += ['pixman-mmx.c']
if use_sse2:
DEFINES['USE_SSE'] = True
DEFINES['USE_SSE2'] = True
SOURCES += ['pixman-sse2.c']
if use_vmx:
DEFINES['USE_VMX'] = True
SOURCES += ['pixman-vmx.c']
if use_arm_simd_gcc:
DEFINES['USE_ARM_SIMD'] = True
SOURCES += ['pixman-arm-simd.c']
if use_arm_neon_gcc:
DEFINES['USE_ARM_NEON'] = True
SOURCES += ['pixman-arm-neon.c']

View File

@ -203,11 +203,6 @@ DIST_GARBAGE = config.cache config.log config.status* \
distclean::
$(RM) $(DIST_GARBAGE)
ifneq (,$(filter WINNT,$(OS_ARCH)))
# _CRT_RAND_S must be #defined before #including stdlib.h to get rand_s()
DEFINES += -D_CRT_RAND_S
endif
ifneq ($(findstring -L,$(NSPR_LIBS)),)
NSPR_STATIC_PATH = $(subst -L,,$(findstring -L,$(NSPR_LIBS)))
else

View File

@ -343,6 +343,8 @@ if CONFIG['OS_ARCH'] == 'WINNT':
'assembler/jit/ExecutableAllocatorWin.cpp',
'yarr/OSAllocatorWin.cpp',
]
# _CRT_RAND_S must be #defined before #including stdlib.h to get rand_s()
DEFINES['_CRT_RAND_S'] = True
else:
SOURCES += [
'assembler/jit/ExecutableAllocatorPosix.cpp',

View File

@ -22,7 +22,7 @@ IntPointDtorFunc(void *aObject, nsIAtom *aPropertyName,
}
#define nsImageControlFrameSuper nsImageFrame
typedef nsImageFrame nsImageControlFrameSuper;
class nsImageControlFrame : public nsImageControlFrameSuper,
public nsIFormControlFrame
{
@ -30,7 +30,7 @@ public:
nsImageControlFrame(nsStyleContext* aContext);
~nsImageControlFrame();
virtual void DestroyFrom(nsIFrame* aDestructRoot);
virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE;
virtual void Init(nsIContent* aContent,
nsIFrame* aParent,
nsIFrame* aPrevInFlow) MOZ_OVERRIDE;
@ -41,29 +41,30 @@ public:
virtual nsresult Reflow(nsPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus);
nsReflowStatus& aStatus) MOZ_OVERRIDE;
virtual nsresult HandleEvent(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus);
nsEventStatus* aEventStatus) MOZ_OVERRIDE;
virtual nsIAtom* GetType() const;
virtual nsIAtom* GetType() const MOZ_OVERRIDE;
#ifdef ACCESSIBILITY
virtual mozilla::a11y::AccType AccessibleType() MOZ_OVERRIDE;
#endif
#ifdef DEBUG_FRAME_DUMP
virtual nsresult GetFrameName(nsAString& aResult) const {
virtual nsresult GetFrameName(nsAString& aResult) const MOZ_OVERRIDE {
return MakeFrameName(NS_LITERAL_STRING("ImageControl"), aResult);
}
#endif
virtual nsresult GetCursor(const nsPoint& aPoint,
nsIFrame::Cursor& aCursor);
nsIFrame::Cursor& aCursor) MOZ_OVERRIDE;
// nsIFormContromFrame
virtual void SetFocus(bool aOn, bool aRepaint);
virtual nsresult SetFormProperty(nsIAtom* aName, const nsAString& aValue);
virtual void SetFocus(bool aOn, bool aRepaint) MOZ_OVERRIDE;
virtual nsresult SetFormProperty(nsIAtom* aName,
const nsAString& aValue) MOZ_OVERRIDE;
};

View File

@ -627,24 +627,53 @@ nsNumberControlFrame::GetValueOfAnonTextControl(nsAString& aValue)
HTMLInputElement::FromContent(mTextField)->GetValue(aValue);
#ifdef ENABLE_INTL_API
// Here we check if the text field's value is a localized serialization of a
// number. If it is we set aValue to the de-localize value, but only if the
// localized value isn't also a valid floating-point number according to the
// HTML 5 spec:
// Here we need to de-localize any number typed in by the user. That is, we
// need to convert it from the number format of the user's language, region,
// etc. to the format that the HTML 5 spec defines to be a "valid
// floating-point number":
//
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#floating-point-numbers
//
// This is because content (and tests) expect us to avoid "normalizing" the
// number that the user types in if it's not necessary. (E.g. if the user
// types "2e2" then inputElement.value should be "2e2" and not "100".
// so that it can be parsed by functions like HTMLInputElement::
// StringToDecimal (the HTML-5-conforming parsing function) which don't know
// how to handle numbers that are formatted differently (for example, with
// non-ASCII digits, with grouping separator characters or with a decimal
// separator character other than '.').
//
// We need to be careful to avoid normalizing numbers that are already
// formatted for a locale that matches the format of HTML 5's "valid
// floating-point number" and have no grouping separator characters. (In
// other words we want to return the number as specified by the user, not the
// de-localized serialization, since the latter will normalize the value.)
// For example, if the user's locale is English and the user types in "2e2"
// then inputElement.value should be "2e2" and not "100". This is because
// content (and tests) expect us to avoid "normalizing" the number that the
// user types in if it's not necessary in order to make sure it conforms to
// HTML 5's "valid floating-point number" format.
//
// Note that we also need to be careful when trying to avoid normalization.
// For example, just because "1.234" _looks_ like a valid floating-point
// number according to the spec does not mean that it should be returned
// as-is. If the user's locale is German, then this represents the value
// 1234, not 1.234, so it still needs to be de-localized. Alternatively, if
// the user's locale is English and they type in "1,234" we _do_ need to
// normalize the number to "1234" because HTML 5's valid floating-point
// number format does not allow the ',' grouping separator. We can detect all
// the cases where we need to convert by seeing if the locale-specific
// parsing function understands the user input to mean the same thing as the
// HTML-5-conforming parsing function. If so, then we should return the value
// as-is to avoid normalization. Otherwise, we return the de-localized
// serialization.
ICUUtils::LanguageTagIterForContent langTagIter(mContent);
double value = ICUUtils::ParseNumber(aValue, langTagIter);
if (NS_finite(value) &&
!HTMLInputElement::StringToDecimal(aValue).isFinite()) {
value != HTMLInputElement::StringToDecimal(aValue).toDouble()) {
aValue.Truncate();
aValue.AppendFloat(value);
}
#endif
// else, we return whatever FromContent put into aValue (the number as typed
// in by the user)
}
bool

View File

@ -10,7 +10,7 @@
#include "nsContentCreatorFunctions.h"
#include "nsContentList.h"
#include "nsContentUtils.h"
#include "nsCSSRenderingBorders.h"
#include "nsCSSRendering.h"
#include "nsFormControlFrame.h"
#include "nsIContent.h"
#include "nsIDocument.h"
@ -82,6 +82,14 @@ nsRangeFrame::Init(nsIContent* aContent,
}
}
}
nsStyleSet *styleSet = PresContext()->StyleSet();
mOuterFocusStyle =
styleSet->ProbePseudoElementStyle(aContent->AsElement(),
nsCSSPseudoElements::ePseudo_mozFocusOuter,
StyleContext());
return nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
}
@ -166,45 +174,39 @@ public:
MOZ_COUNT_DTOR(nsDisplayRangeFocusRing);
}
#endif
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) MOZ_OVERRIDE;
virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) MOZ_OVERRIDE;
NS_DISPLAY_DECL_NAME("RangeFocusRing", TYPE_OUTLINE)
};
nsRect
nsDisplayRangeFocusRing::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
{
*aSnap = false;
nsRect rect(ToReferenceFrame(), Frame()->GetSize());
// We want to paint as if specifying a border for ::-moz-focus-outer
// specifies an outline for our frame, so inflate by the border widths:
nsStyleContext* styleContext =
static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle;
MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null");
rect.Inflate(styleContext->StyleBorder()->GetComputedBorder());
return rect;
}
void
nsDisplayRangeFocusRing::Paint(nsDisplayListBuilder* aBuilder,
nsRenderingContext* aCtx)
{
nsPresContext *presContext = mFrame->PresContext();
nscoord appUnitsPerDevPixel = presContext->DevPixelsToAppUnits(1);
gfxContext* ctx = aCtx->ThebesContext();
nsRect r = nsRect(ToReferenceFrame(), mFrame->GetSize());
gfxRect pxRect(nsLayoutUtils::RectToGfxRect(r, appUnitsPerDevPixel));
uint8_t borderStyles[4] = { NS_STYLE_BORDER_STYLE_DOTTED,
NS_STYLE_BORDER_STYLE_DOTTED,
NS_STYLE_BORDER_STYLE_DOTTED,
NS_STYLE_BORDER_STYLE_DOTTED };
gfxFloat borderWidths[4] = { 1, 1, 1, 1 };
gfxCornerSizes borderRadii(0);
nscolor borderColors[4] = { NS_RGB(0, 0, 0), NS_RGB(0, 0, 0),
NS_RGB(0, 0, 0), NS_RGB(0, 0, 0) };
nsStyleContext* bgContext = mFrame->StyleContext();
nscolor bgColor =
bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
ctx->Save();
nsCSSBorderRenderer br(appUnitsPerDevPixel,
ctx,
pxRect,
borderStyles,
borderWidths,
borderRadii,
borderColors,
nullptr,
0,
bgColor);
br.DrawBorders();
ctx->Restore();
bool unused;
nsStyleContext* styleContext =
static_cast<nsRangeFrame*>(mFrame)->mOuterFocusStyle;
MOZ_ASSERT(styleContext, "We only exist if mOuterFocusStyle is non-null");
nsCSSRendering::PaintBorder(mFrame->PresContext(), *aCtx, mFrame,
mVisibleRect, GetBounds(aBuilder, &unused),
styleContext);
}
void
@ -231,19 +233,35 @@ nsRangeFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
}
// Draw a focus outline if appropriate:
nsEventStates eventStates = mContent->AsElement()->State();
if (!eventStates.HasState(NS_EVENT_STATE_FOCUSRING) ||
eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
if (!aBuilder->IsForPainting() ||
!IsVisibleForPainting(aBuilder)) {
// we don't want the focus ring item for hit-testing or if the item isn't
// in the area being [re]painted
return;
}
nsPresContext *presContext = PresContext();
const nsStyleDisplay *disp = StyleDisplay();
if ((!IsThemed(disp) ||
!presContext->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) &&
IsVisibleForPainting(aBuilder)) {
aLists.Content()->AppendNewToTop(
new (aBuilder) nsDisplayRangeFocusRing(aBuilder, this));
nsEventStates eventStates = mContent->AsElement()->State();
if (eventStates.HasState(NS_EVENT_STATE_DISABLED) ||
!eventStates.HasState(NS_EVENT_STATE_FOCUSRING)) {
return; // can't have focus or doesn't match :-moz-focusring
}
if (!mOuterFocusStyle ||
!mOuterFocusStyle->StyleBorder()->HasBorder()) {
// no ::-moz-focus-outer specified border (how style specifies a focus ring
// for range)
return;
}
const nsStyleDisplay *disp = StyleDisplay();
if (IsThemed(disp) &&
PresContext()->GetTheme()->ThemeDrawsFocusForWidget(disp->mAppearance)) {
return; // the native theme displays its own visual indication of focus
}
aLists.Content()->AppendNewToTop(
new (aBuilder) nsDisplayRangeFocusRing(aBuilder, this));
}
nsresult
@ -845,3 +863,23 @@ nsRangeFrame::GetPseudoElement(nsCSSPseudoElements::Type aType)
return nsContainerFrame::GetPseudoElement(aType);
}
nsStyleContext*
nsRangeFrame::GetAdditionalStyleContext(int32_t aIndex) const
{
// We only implement this so that SetAdditionalStyleContext will be
// called if style changes that would change the -moz-focus-outer
// pseudo-element have occurred.
return aIndex == 0 ? mOuterFocusStyle : nullptr;
}
void
nsRangeFrame::SetAdditionalStyleContext(int32_t aIndex,
nsStyleContext* aStyleContext)
{
MOZ_ASSERT(aIndex == 0,
"GetAdditionalStyleContext is handling other indexes?");
// The -moz-focus-outer pseudo-element's style has changed.
mOuterFocusStyle = aStyleContext;
}

View File

@ -14,6 +14,7 @@
#include "nsCOMPtr.h"
class nsBaseContentList;
class nsDisplayRangeFocusRing;
class nsRangeFrame : public nsContainerFrame,
public nsIAnonymousContentCreator
@ -21,6 +22,8 @@ class nsRangeFrame : public nsContainerFrame,
friend nsIFrame*
NS_NewRangeFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
friend class nsDisplayRangeFocusRing;
nsRangeFrame(nsStyleContext* aContext);
virtual ~nsRangeFrame();
@ -84,6 +87,10 @@ public:
~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
}
nsStyleContext* GetAdditionalStyleContext(int32_t aIndex) const MOZ_OVERRIDE;
void SetAdditionalStyleContext(int32_t aIndex,
nsStyleContext* aStyleContext) MOZ_OVERRIDE;
/**
* Returns true if the slider's thumb moves horizontally, or else false if it
* moves vertically.
@ -162,6 +169,11 @@ private:
* @see nsRangeFrame::CreateAnonymousContent
*/
nsCOMPtr<Element> mThumbDiv;
/**
* Cached style context for -moz-focus-outer CSS pseudo-element style.
*/
nsRefPtr<nsStyleContext> mOuterFocusStyle;
};
#endif

View File

@ -70,7 +70,7 @@ class nsFirstLineFrame;
* prepended to the overflow lines.
*/
#define nsBlockFrameSuper nsContainerFrame
typedef nsContainerFrame nsBlockFrameSuper;
/*
* Base class for block and inline frames.

View File

@ -54,7 +54,7 @@ private:
nsImageFrame *mFrame;
};
#define ImageFrameSuper nsSplittableFrame
typedef nsSplittableFrame ImageFrameSuper;
class nsImageFrame : public ImageFrameSuper,
public nsIReflowCallback {
@ -70,47 +70,48 @@ public:
NS_DECL_QUERYFRAME_TARGET(nsImageFrame)
NS_DECL_QUERYFRAME
virtual void DestroyFrom(nsIFrame* aDestructRoot);
virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE;
virtual void Init(nsIContent* aContent,
nsIFrame* aParent,
nsIFrame* aPrevInFlow) MOZ_OVERRIDE;
virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
const nsDisplayListSet& aLists) MOZ_OVERRIDE;
virtual nscoord GetMinWidth(nsRenderingContext *aRenderingContext);
virtual nscoord GetPrefWidth(nsRenderingContext *aRenderingContext);
virtual mozilla::IntrinsicSize GetIntrinsicSize();
virtual nsSize GetIntrinsicRatio();
virtual nscoord GetMinWidth(nsRenderingContext *aRenderingContext) MOZ_OVERRIDE;
virtual nscoord GetPrefWidth(nsRenderingContext *aRenderingContext) MOZ_OVERRIDE;
virtual mozilla::IntrinsicSize GetIntrinsicSize() MOZ_OVERRIDE;
virtual nsSize GetIntrinsicRatio() MOZ_OVERRIDE;
virtual nsresult Reflow(nsPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus);
nsReflowStatus& aStatus) MOZ_OVERRIDE;
virtual nsresult GetContentForEvent(mozilla::WidgetEvent* aEvent,
nsIContent** aContent);
nsIContent** aContent) MOZ_OVERRIDE;
virtual nsresult HandleEvent(nsPresContext* aPresContext,
mozilla::WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus);
nsEventStatus* aEventStatus) MOZ_OVERRIDE;
virtual nsresult GetCursor(const nsPoint& aPoint,
nsIFrame::Cursor& aCursor);
nsIFrame::Cursor& aCursor) MOZ_OVERRIDE;
virtual nsresult AttributeChanged(int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType);
int32_t aModType) MOZ_OVERRIDE;
#ifdef ACCESSIBILITY
virtual mozilla::a11y::AccType AccessibleType() MOZ_OVERRIDE;
#endif
virtual nsIAtom* GetType() const;
virtual nsIAtom* GetType() const MOZ_OVERRIDE;
virtual bool IsFrameOfType(uint32_t aFlags) const
virtual bool IsFrameOfType(uint32_t aFlags) const MOZ_OVERRIDE
{
return ImageFrameSuper::IsFrameOfType(aFlags & ~(nsIFrame::eReplaced));
}
#ifdef DEBUG_FRAME_DUMP
virtual nsresult GetFrameName(nsAString& aResult) const;
void List(FILE* out = stderr, const char* aPrefix = "", uint32_t aFlags = 0) const;
virtual nsresult GetFrameName(nsAString& aResult) const MOZ_OVERRIDE;
void List(FILE* out = stderr, const char* aPrefix = "",
uint32_t aFlags = 0) const MOZ_OVERRIDE;
#endif
virtual int GetSkipSides(const nsHTMLReflowState* aReflowState = nullptr) const MOZ_OVERRIDE;
@ -163,7 +164,7 @@ public:
nsImageMap* GetExistingImageMap() const { return mImageMap; }
virtual void AddInlineMinWidth(nsRenderingContext *aRenderingContext,
InlineMinWidthData *aData);
InlineMinWidthData *aData) MOZ_OVERRIDE;
void DisconnectMap();

View File

@ -34,7 +34,7 @@ class LayerManager;
}
}
#define nsObjectFrameSuper nsFrame
typedef nsFrame nsObjectFrameSuper;
class nsObjectFrame : public nsObjectFrameSuper,
public nsIObjectFrame,
@ -56,45 +56,46 @@ public:
virtual void Init(nsIContent* aContent,
nsIFrame* aParent,
nsIFrame* aPrevInFlow) MOZ_OVERRIDE;
virtual nscoord GetMinWidth(nsRenderingContext *aRenderingContext);
virtual nscoord GetPrefWidth(nsRenderingContext *aRenderingContext);
virtual nscoord GetMinWidth(nsRenderingContext *aRenderingContext) MOZ_OVERRIDE;
virtual nscoord GetPrefWidth(nsRenderingContext *aRenderingContext) MOZ_OVERRIDE;
virtual nsresult Reflow(nsPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus);
nsReflowStatus& aStatus) MOZ_OVERRIDE;
virtual nsresult DidReflow(nsPresContext* aPresContext,
const nsHTMLReflowState* aReflowState,
nsDidReflowStatus aStatus);
nsDidReflowStatus aStatus) MOZ_OVERRIDE;
virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
const nsDisplayListSet& aLists) MOZ_OVERRIDE;
virtual nsresult HandleEvent(nsPresContext* aPresContext,
mozilla::WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus);
nsEventStatus* aEventStatus) MOZ_OVERRIDE;
virtual nsIAtom* GetType() const;
virtual nsIAtom* GetType() const MOZ_OVERRIDE;
virtual bool IsFrameOfType(uint32_t aFlags) const
virtual bool IsFrameOfType(uint32_t aFlags) const MOZ_OVERRIDE
{
return nsObjectFrameSuper::IsFrameOfType(aFlags & ~(nsIFrame::eReplaced));
}
virtual bool NeedsView() { return true; }
virtual bool NeedsView() MOZ_OVERRIDE { return true; }
#ifdef DEBUG_FRAME_DUMP
virtual nsresult GetFrameName(nsAString& aResult) const;
virtual nsresult GetFrameName(nsAString& aResult) const MOZ_OVERRIDE;
#endif
virtual void DestroyFrom(nsIFrame* aDestructRoot);
virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE;
virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext);
virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext) MOZ_OVERRIDE;
NS_METHOD GetPluginInstance(nsNPAPIPluginInstance** aPluginInstance) MOZ_OVERRIDE;
virtual void SetIsDocumentActive(bool aIsActive) MOZ_OVERRIDE;
virtual nsresult GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor);
virtual nsresult GetCursor(const nsPoint& aPoint,
nsIFrame::Cursor& aCursor) MOZ_OVERRIDE;
// APIs used by nsRootPresContext to set up the widget position/size/clip
// region.
@ -213,7 +214,8 @@ protected:
const nsHTMLReflowState& aReflowState,
nsHTMLReflowMetrics& aDesiredSize);
bool IsFocusable(int32_t *aTabIndex = nullptr, bool aWithMouse = false);
bool IsFocusable(int32_t *aTabIndex = nullptr,
bool aWithMouse = false) MOZ_OVERRIDE;
// check attributes and optionally CSS to see if we should display anything
bool IsHidden(bool aCheckVisibilityStyle = true) const;

View File

@ -805,6 +805,16 @@ input[type=range][orient=vertical] {
height: 12em;
}
/**
* Ideally we'd also require :-moz-focusring here, but that doesn't currently
* work. Instead we only use the -moz-focus-outer border style if
* NS_EVENT_STATE_FOCUSRING is set (the check is in
* nsRangeFrame::BuildDisplayList).
*/
input[type=range]::-moz-focus-outer {
border: 1px dotted black;
}
/**
* Layout handles positioning of this pseudo-element specially (so that content
* authors can concentrate on styling the thumb without worrying about the

View File

@ -108,8 +108,7 @@ nsTableCellMap::GetRightMostBorder(int32_t aRowIndex)
return &mBCInfo->mRightBorders.ElementAt(aRowIndex);
}
if (!mBCInfo->mRightBorders.SetLength(aRowIndex+1))
ABORT1(nullptr);
mBCInfo->mRightBorders.SetLength(aRowIndex+1);
return &mBCInfo->mRightBorders.ElementAt(aRowIndex);
}
@ -124,8 +123,7 @@ nsTableCellMap::GetBottomMostBorder(int32_t aColIndex)
return &mBCInfo->mBottomBorders.ElementAt(aColIndex);
}
if (!mBCInfo->mBottomBorders.SetLength(aColIndex+1))
ABORT1(nullptr);
mBCInfo->mBottomBorders.SetLength(aColIndex+1);
return &mBCInfo->mBottomBorders.ElementAt(aColIndex);
}
@ -488,15 +486,13 @@ nsTableCellMap::InsertRows(nsTableRowGroupFrame* aParent,
int32_t count = mBCInfo->mRightBorders.Length();
if (aFirstRowIndex < count) {
for (int32_t rowX = aFirstRowIndex; rowX < aFirstRowIndex + numNewRows; rowX++) {
if (!mBCInfo->mRightBorders.InsertElementAt(rowX))
ABORT0();
mBCInfo->mRightBorders.InsertElementAt(rowX);
}
}
else {
GetRightMostBorder(aFirstRowIndex); // this will create missing entries
for (int32_t rowX = aFirstRowIndex + 1; rowX < aFirstRowIndex + numNewRows; rowX++) {
if (!mBCInfo->mRightBorders.AppendElement())
ABORT0();
mBCInfo->mRightBorders.AppendElement();
}
}
}

View File

@ -19,6 +19,9 @@ DEFINES['OPUS_BUILD'] = True
DEFINES['OPUS_VERSION'] = '"v1.1-mozilla"'
DEFINES['USE_ALLOCA'] = True
if CONFIG['MOZ_DEBUG']:
DEFINES['ENABLE_ASSERTIONS'] = True
if CONFIG['OS_ARCH'] in ('Linux', 'Darwin', 'DragonFly', 'FreeBSD',
'NetBSD', 'OpenBSD'):
DEFINES['HAVE_LRINTF'] = True

View File

@ -51,9 +51,10 @@ GARBAGE += vp8_asm_enc_offsets.$(OBJ_SUFFIX) vp8_asm_enc_offsets.asm
else
# We can extract the asm offsets directly from generated assembly using inline
# asm. This is the preferred method.
# asm. This is the preferred method. However we need to strip out CFLAGS that
# cause LTO because then the resulting .S file is useless.
vpx_scale_asm_offsets.s: CFLAGS += -DINLINE_ASM
vpx_scale_asm_offsets.s: CFLAGS := -DINLINE_ASM
OFFSET_PATTERN := '^[a-zA-Z0-9_]* EQU'
@ -70,7 +71,7 @@ vpx_scale_asm_offsets.asm: vpx_scale_asm_offsets.s
GARBAGE += vpx_scale_asm_offsets.s vpx_scale_asm_offsets.asm
vp8_asm_enc_offsets.s: CFLAGS += -DINLINE_ASM
vp8_asm_enc_offsets.s: CFLAGS := -DINLINE_ASM
vp8_asm_enc_offsets.s: $(srcdir)/vp8/encoder/vp8_asm_enc_offsets.c
$(REPORT_BUILD)

View File

@ -6,8 +6,8 @@
<property name="objdir" value="@IDE_OBJDIR@"/>
<property name="project_name" value="@IDE_PROJECT_NAME@"/>
// This file can get large (!), but for a short time we want to
// log as much information for debugging build loops as possible.
<!-- This file can get large (!), but for a short time we want to
log as much information for debugging build loops as possible. -->
<record name="${topobjdir}/android_eclipse/build.log" append="yes" />
<target name="build_needed" >

View File

@ -605,7 +605,7 @@ VerifySignature(AppTrustedRoot trustedRoot,
}
if (BuildCertChain(trustDomain, signerCert, PR_Now(), MustBeEndEntity,
KU_DIGITAL_SIGNATURE, SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
builtChain) != SECSuccess) {
nullptr, builtChain) != SECSuccess) {
return MapSECStatus(SECFailure);
}

View File

@ -178,4 +178,16 @@ AppTrustDomain::VerifySignedData(const CERTSignedData* signedData,
return ::insanity::pkix::VerifySignedData(signedData, cert, mPinArg);
}
SECStatus
AppTrustDomain::CheckRevocation(EndEntityOrCA,
const CERTCertificate*,
/*const*/ CERTCertificate*,
PRTime time,
/*optional*/ const SECItem*)
{
// We don't currently do revocation checking. If we need to distrust an Apps
// certificate, we will use the active distrust mechanism.
return SECSuccess;
}
} }

View File

@ -29,7 +29,11 @@ public:
MOZ_OVERRIDE;
SECStatus VerifySignedData(const CERTSignedData* signedData,
const CERTCertificate* cert) MOZ_OVERRIDE;
SECStatus CheckRevocation(insanity::pkix::EndEntityOrCA endEntityOrCA,
const CERTCertificate* cert,
/*const*/ CERTCertificate* issuerCertToDup,
PRTime time,
/*optional*/ const SECItem* stapledOCSPresponse);
private:
void* mPinArg; // non-owning!
insanity::pkix::ScopedCERTCertificate mTrustedRoot;

View File

@ -23,7 +23,7 @@ using namespace insanity::pkix;
using namespace mozilla::psm;
#ifdef PR_LOGGING
static PRLogModuleInfo* gCertVerifierLog = nullptr;
PRLogModuleInfo* gCertVerifierLog = nullptr;
#endif
namespace mozilla { namespace psm {
@ -194,21 +194,22 @@ static SECStatus
BuildCertChainForOneKeyUsage(TrustDomain& trustDomain, CERTCertificate* cert,
PRTime time, KeyUsages ku1, KeyUsages ku2,
KeyUsages ku3, SECOidTag eku,
const SECItem* stapledOCSPResponse,
ScopedCERTCertList& builtChain)
{
PR_ASSERT(ku1);
PR_ASSERT(ku2);
SECStatus rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
ku1, eku, builtChain);
ku1, eku, stapledOCSPResponse, builtChain);
if (rv != SECSuccess && ku2 &&
PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
ku2, eku, builtChain);
ku2, eku, stapledOCSPResponse, builtChain);
if (rv != SECSuccess && ku3 &&
PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
ku3, eku, builtChain);
ku3, eku, stapledOCSPResponse, builtChain);
if (rv != SECSuccess) {
PR_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE, 0);
}
@ -220,11 +221,11 @@ BuildCertChainForOneKeyUsage(TrustDomain& trustDomain, CERTCertificate* cert,
SECStatus
CertVerifier::InsanityVerifyCert(
CERTCertificate* cert,
/*optional*/ const SECItem* /*TODO: stapledOCSPResponse*/,
const SECCertificateUsage usage,
const PRTime time,
void* pinArg,
const Flags flags,
/*optional*/ const SECItem* stapledOCSPResponse,
/*optional out*/ insanity::pkix::ScopedCERTCertList* validationChain)
{
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("Top of InsanityVerifyCert\n"));
@ -248,7 +249,7 @@ CertVerifier::InsanityVerifyCert(
rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
KU_DIGITAL_SIGNATURE,
SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH,
builtChain);
stapledOCSPResponse, builtChain);
break;
}
@ -263,7 +264,7 @@ CertVerifier::InsanityVerifyCert(
KU_KEY_ENCIPHERMENT, // RSA
KU_KEY_AGREEMENT, // ECDH/DH
SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
builtChain);
stapledOCSPResponse, builtChain);
break;
}
@ -273,7 +274,7 @@ CertVerifier::InsanityVerifyCert(
rv = BuildCertChain(trustDomain, cert, time, MustBeCA,
KU_KEY_CERT_SIGN,
SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
builtChain);
stapledOCSPResponse, builtChain);
break;
}
@ -283,7 +284,7 @@ CertVerifier::InsanityVerifyCert(
rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
KU_DIGITAL_SIGNATURE,
SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
builtChain);
stapledOCSPResponse, builtChain);
break;
}
@ -298,7 +299,7 @@ CertVerifier::InsanityVerifyCert(
KU_KEY_AGREEMENT, // ECDH/DH
0,
SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
builtChain);
stapledOCSPResponse, builtChain);
break;
}
@ -309,7 +310,7 @@ CertVerifier::InsanityVerifyCert(
rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
KU_DIGITAL_SIGNATURE,
SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
builtChain);
stapledOCSPResponse, builtChain);
break;
}
@ -335,18 +336,18 @@ CertVerifier::InsanityVerifyCert(
NSSCertDBTrustDomain sslTrust(trustSSL,
mOCSPDownloadEnabled, mOCSPStrict, pinArg);
rv = BuildCertChain(sslTrust, cert, time, endEntityOrCA,
keyUsage, eku, builtChain);
keyUsage, eku, stapledOCSPResponse, builtChain);
if (rv == SECFailure && PR_GetError() == SEC_ERROR_UNKNOWN_ISSUER) {
NSSCertDBTrustDomain emailTrust(trustEmail, mOCSPDownloadEnabled,
mOCSPStrict, pinArg);
rv = BuildCertChain(emailTrust, cert, time, endEntityOrCA, keyUsage,
eku, builtChain);
eku, stapledOCSPResponse, builtChain);
if (rv == SECFailure && SEC_ERROR_UNKNOWN_ISSUER) {
NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
mOCSPDownloadEnabled,
mOCSPStrict, pinArg);
rv = BuildCertChain(objectSigningTrust, cert, time, endEntityOrCA,
keyUsage, eku, builtChain);
keyUsage, eku, stapledOCSPResponse, builtChain);
}
}
@ -600,8 +601,8 @@ CertVerifier::VerifyCert(CERTCertificate* cert,
}
if (mImplementation == insanity) {
return InsanityVerifyCert(cert, stapledOCSPResponse, usage, time,
pinArg, flags, validationChain);
return InsanityVerifyCert(cert, usage, time, pinArg, flags,
stapledOCSPResponse, validationChain);
}
if (mImplementation == classic) {

View File

@ -78,11 +78,11 @@ public:
private:
SECStatus InsanityVerifyCert(CERTCertificate* cert,
/*optional*/ const SECItem* stapledOCSPResponse,
const SECCertificateUsage usage,
const PRTime time,
void* pinArg,
const Flags flags,
/*optional*/ const SECItem* stapledOCSPResponse,
/*optional out*/ insanity::pkix::ScopedCERTCertList* validationChain);
};

View File

@ -38,12 +38,12 @@ typedef ScopedPtr<SECMODModule, SECMOD_DestroyModule> ScopedSECMODModule;
} // unnamed namespace
NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType,
bool /*ocspDownloadEnabled*/,
bool /*ocspStrict*/,
bool ocspDownloadEnabled,
bool ocspStrict,
void* pinArg)
: mCertDBTrustType(certDBTrustType)
// , mOCSPDownloadEnabled(ocspDownloadEnabled)
// , mOCSPStrict(ocspStrict)
, mOCSPDownloadEnabled(ocspDownloadEnabled)
, mOCSPStrict(ocspStrict)
, mPinArg(pinArg)
{
}
@ -124,6 +124,118 @@ NSSCertDBTrustDomain::VerifySignedData(const CERTSignedData* signedData,
return ::insanity::pkix::VerifySignedData(signedData, cert, mPinArg);
}
SECStatus
NSSCertDBTrustDomain::CheckRevocation(
insanity::pkix::EndEntityOrCA endEntityOrCA,
const CERTCertificate* cert,
/*const*/ CERTCertificate* issuerCert,
PRTime time,
/*optional*/ const SECItem* stapledOCSPResponse)
{
// Actively distrusted certificates will have already been blocked by
// GetCertTrust.
// TODO: need to verify that IsRevoked isn't called for trust anchors AND
// that that fact is documented in insanity.
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("NSSCertDBTrustDomain: Top of CheckRevocation\n"));
PORT_Assert(cert);
PORT_Assert(issuerCert);
if (!cert || !issuerCert) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
// If we have a stapled OCSP response then the verification of that response
// determines the result unless the OCSP response is expired. We make an
// exception for expired responses because some servers, nginx in particular,
// are known to serve expired responses due to bugs.
if (stapledOCSPResponse) {
PR_ASSERT(endEntityOrCA == MustBeEndEntity);
SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
stapledOCSPResponse);
if (rv == SECSuccess) {
return rv;
}
if (PR_GetError() != SEC_ERROR_OCSP_OLD_RESPONSE) {
return rv;
}
}
// TODO(bug 921885): We need to change this when we add EV support.
// TODO: when !mOCSPDownloadEnabled, we still need to handle the fallback for
// expired responses. But, if/when we disable OCSP fetching by default, it
// would be ambiguous whether !mOCSPDownloadEnabled means "I want the default"
// or "I really never want you to ever fetch OCSP."
if (mOCSPDownloadEnabled) {
// We don't do OCSP fetching for intermediates.
if (endEntityOrCA == MustBeCA) {
PR_ASSERT(!stapledOCSPResponse);
return SECSuccess;
}
ScopedPtr<char, PORT_Free_string>
url(CERT_GetOCSPAuthorityInfoAccessLocation(cert));
// Nothing to do if we don't have an OCSP responder URI for the cert; just
// assume it is good. Note that this is the confusing, but intended,
// interpretation of "strict" revocation checking in the face of a
// certificate that lacks an OCSP responder URI.
if (!url) {
if (stapledOCSPResponse) {
PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
return SECFailure;
}
return SECSuccess;
}
ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
if (!arena) {
return SECFailure;
}
const SECItem* request
= CreateEncodedOCSPRequest(arena.get(), cert, issuerCert);
if (!request) {
return SECFailure;
}
const SECItem* response(CERT_PostOCSPRequest(arena.get(), url.get(),
request));
if (!response) {
if (mOCSPStrict) {
return SECFailure;
}
// Soft fail -> success :(
} else {
SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
response);
if (rv == SECSuccess) {
return SECSuccess;
}
PRErrorCode error = PR_GetError();
switch (error) {
case SEC_ERROR_OCSP_UNKNOWN_CERT:
case SEC_ERROR_REVOKED_CERTIFICATE:
return SECFailure;
default:
if (mOCSPStrict) {
return SECFailure;
}
break; // Soft fail -> success :(
}
}
}
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("NSSCertDBTrustDomain: end of CheckRevocation"));
return SECSuccess;
}
namespace {
static char*

View File

@ -63,10 +63,16 @@ public:
virtual SECStatus VerifySignedData(const CERTSignedData* signedData,
const CERTCertificate* cert);
virtual SECStatus CheckRevocation(insanity::pkix::EndEntityOrCA endEntityOrCA,
const CERTCertificate* cert,
/*const*/ CERTCertificate* issuerCert,
PRTime time,
/*optional*/ const SECItem* stapledOCSPResponse);
private:
const SECTrustType mCertDBTrustType;
// const bool mOCSPDownloadEnabled;
// const bool mOCSPStrict;
const bool mOCSPDownloadEnabled;
const bool mOCSPStrict;
void* mPinArg; // non-owning!
};

View File

@ -9,6 +9,7 @@ UNIFIED_SOURCES += [
'../insanity/lib/pkixcheck.cpp',
'../insanity/lib/pkixder.cpp',
'../insanity/lib/pkixkey.cpp',
'../insanity/lib/pkixocsp.cpp',
'CertVerifier.cpp',
'NSSCertDBTrustDomain.cpp',
]

View File

@ -23,12 +23,55 @@
namespace insanity { namespace pkix {
// ----------------------------------------------------------------------------
// ERROR RANKING
//
// BuildCertChain prioritizes certain checks ahead of others so that when a
// certificate chain has multiple errors, the "most serious" error is
// returned. In practice, this ranking of seriousness is tied directly to how
// Firefox's certificate error override mechanism.
//
// The ranking is:
//
// 1. Active distrust (SEC_ERROR_UNTRUSTED_CERT).
// 2. Problems with issuer-independent properties other than
// notBefore/notAfter.
// 3. For CA certificates: Expiration.
// 4. Unknown issuer (SEC_ERROR_UNKNOWN_ISSUER).
// 5. For end-entity certificates: Expiration.
// 6. Revocation.
//
// In particular, if BuildCertChain returns SEC_ERROR_UNKNOWN_ISSUER then the
// caller can call CERT_CheckCertValidTimes to determine if the certificate is
// ALSO expired.
//
// It would be better if revocation were prioritized above expiration and
// unknown issuer. However, it is impossible to do revocation checking without
// knowing the issuer, since the issuer information is needed to validate the
// revocation information. Also, generally revocation checking only works
// during the validity period of the certificate.
//
// In general, when path building fails, BuildCertChain will return
// SEC_ERROR_UNKNOWN_ISSUER. However, if all attempted paths resulted in the
// same error (which is trivially true when there is only one potential path),
// more specific errors will be returned.
//
// ----------------------------------------------------------------------------
// Meaning of specific error codes
//
// SEC_ERROR_UNTRUSTED_CERT means that the end-entity certificate was actively
// distrusted.
// SEC_ERROR_UNTRUSTED_ISSUER means that path building failed because of active
// distrust.
// TODO(bug 968451): Document more of these.
SECStatus BuildCertChain(TrustDomain& trustDomain,
CERTCertificate* cert,
PRTime time,
EndEntityOrCA endEntityOrCA,
/*optional*/ KeyUsages requiredKeyUsagesIfPresent,
/*optional*/ SECOidTag requiredEKUIfPresent,
/*optional*/ const SECItem* stapledOCSPResponse,
/*out*/ ScopedCERTCertList& results);
// Verify the given signed data using the public key of the given certificate.
@ -37,6 +80,17 @@ SECStatus VerifySignedData(const CERTSignedData* sd,
const CERTCertificate* cert,
void* pkcs11PinArg);
// The return value, if non-null, is owned by the arena and MUST NOT be freed.
SECItem* CreateEncodedOCSPRequest(PLArenaPool* arena,
const CERTCertificate* cert,
const CERTCertificate* issuerCert);
SECStatus VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
const CERTCertificate* cert,
CERTCertificate* issuerCert,
PRTime time,
const SECItem* encodedResponse);
} } // namespace insanity::pkix
#endif // insanity_pkix__pkix_h

View File

@ -79,6 +79,14 @@ public:
virtual SECStatus VerifySignedData(const CERTSignedData* signedData,
const CERTCertificate* cert) = 0;
// issuerCertToDup is only non-const so CERT_DupCertificate can be called on
// it.
virtual SECStatus CheckRevocation(EndEntityOrCA endEntityOrCA,
const CERTCertificate* cert,
/*const*/ CERTCertificate* issuerCertToDup,
PRTime time,
/*optional*/ const SECItem* stapledOCSPresponse) = 0;
protected:
TrustDomain() { }

View File

@ -96,6 +96,7 @@ static Result BuildForward(TrustDomain& trustDomain,
EndEntityOrCA endEntityOrCA,
KeyUsages requiredKeyUsagesIfPresent,
SECOidTag requiredEKUIfPresent,
/*optional*/ const SECItem* stapledOCSPResponse,
unsigned int subCACount,
/*out*/ ScopedCERTCertList& results);
@ -107,6 +108,7 @@ BuildForwardInner(TrustDomain& trustDomain,
EndEntityOrCA endEntityOrCA,
SECOidTag requiredEKUIfPresent,
CERTCertificate* potentialIssuerCertToDup,
/*optional*/ const SECItem* stapledOCSPResponse,
unsigned int subCACount,
ScopedCERTCertList& results)
{
@ -136,11 +138,6 @@ BuildForwardInner(TrustDomain& trustDomain,
}
}
rv = CheckTimes(potentialIssuer.GetNSSCert(), time);
if (rv != Success) {
return rv;
}
rv = CheckNameConstraints(potentialIssuer);
if (rv != Success) {
return rv;
@ -152,10 +149,9 @@ BuildForwardInner(TrustDomain& trustDomain,
} else {
PR_ASSERT(newSubCACount == 0);
}
rv = BuildForward(trustDomain, potentialIssuer, time, MustBeCA,
KU_KEY_CERT_SIGN, requiredEKUIfPresent,
newSubCACount, results);
nullptr, newSubCACount, results);
if (rv != Success) {
return rv;
}
@ -168,7 +164,12 @@ BuildForwardInner(TrustDomain& trustDomain,
return Success;
}
// Caller must check for expiration before calling this function
// Recursively build the path from the given subject certificate to the root.
//
// Be very careful about changing the order of checks. The order is significant
// because it affects which error we return when a certificate or certificate
// chain has multiple problems. See the error ranking documentation in
// insanity/pkix.h.
static Result
BuildForward(TrustDomain& trustDomain,
BackCert& subject,
@ -176,6 +177,7 @@ BuildForward(TrustDomain& trustDomain,
EndEntityOrCA endEntityOrCA,
KeyUsages requiredKeyUsagesIfPresent,
SECOidTag requiredEKUIfPresent,
/*optional*/ const SECItem* stapledOCSPResponse,
unsigned int subCACount,
/*out*/ ScopedCERTCertList& results)
{
@ -186,28 +188,26 @@ BuildForward(TrustDomain& trustDomain,
return RecoverableError;
}
TrustDomain::TrustLevel trustLevel;
Result rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA,
subject.GetNSSCert(),
&trustLevel));
if (rv != Success) {
return rv;
}
if (trustLevel == TrustDomain::ActivelyDistrusted) {
return Fail(RecoverableError, SEC_ERROR_UNTRUSTED_CERT);
}
if (trustLevel != TrustDomain::TrustAnchor &&
trustLevel != TrustDomain::InheritsTrust) {
// The TrustDomain returned a trust level that we weren't expecting.
return Fail(FatalError, PR_INVALID_STATE_ERROR);
}
Result rv;
rv = CheckExtensions(subject, endEntityOrCA,
trustLevel == TrustDomain::TrustAnchor,
requiredKeyUsagesIfPresent, requiredEKUIfPresent,
subCACount);
TrustDomain::TrustLevel trustLevel;
bool expiredEndEntity = false;
rv = CheckIssuerIndependentProperties(trustDomain, subject, time,
endEntityOrCA,
requiredKeyUsagesIfPresent,
requiredEKUIfPresent, subCACount,
&trustLevel);
if (rv != Success) {
return rv;
// CheckIssuerIndependentProperties checks for expiration last, so if
// it returned SEC_ERROR_EXPIRED_CERTIFICATE we know that is the only
// problem with the cert found so far. Keep going to see if we can build
// a path; if not, it's better to return the path building failure.
expiredEndEntity = endEntityOrCA == MustBeEndEntity &&
trustLevel != TrustDomain::TrustAnchor &&
PR_GetError() == SEC_ERROR_EXPIRED_CERTIFICATE;
if (!expiredEndEntity) {
return rv;
}
}
if (trustLevel == TrustDomain::TrustAnchor) {
@ -233,21 +233,62 @@ BuildForward(TrustDomain& trustDomain,
return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER);
}
PRErrorCode errorToReturn = 0;
for (CERTCertListNode* n = CERT_LIST_HEAD(candidates);
!CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) {
rv = BuildForwardInner(trustDomain, subject, time, endEntityOrCA,
requiredEKUIfPresent,
n->cert, subCACount, results);
n->cert, stapledOCSPResponse, subCACount,
results);
if (rv == Success) {
if (expiredEndEntity) {
// We deferred returning this error to see if we should return
// "unknown issuer" instead. Since we found a valid issuer, it's
// time to return "expired."
PR_SetError(SEC_ERROR_EXPIRED_CERTIFICATE, 0);
return RecoverableError;
}
SECStatus srv = trustDomain.CheckRevocation(endEntityOrCA,
subject.GetNSSCert(),
n->cert, time,
stapledOCSPResponse);
if (srv != SECSuccess) {
return MapSECStatus(SECFailure);
}
// We found a trusted issuer. At this point, we know the cert is valid
return subject.PrependNSSCertToList(results.get());
}
if (rv != RecoverableError) {
return rv;
}
PRErrorCode currentError = PR_GetError();
switch (currentError) {
case 0:
PR_NOT_REACHED("Error code not set!");
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return FatalError;
case SEC_ERROR_UNTRUSTED_CERT:
currentError = SEC_ERROR_UNTRUSTED_ISSUER;
break;
default:
break;
}
if (errorToReturn == 0) {
errorToReturn = currentError;
} else if (errorToReturn != currentError) {
errorToReturn = SEC_ERROR_UNKNOWN_ISSUER;
}
}
return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER);
if (errorToReturn == 0) {
errorToReturn = SEC_ERROR_UNKNOWN_ISSUER;
}
return Fail(RecoverableError, errorToReturn);
}
SECStatus
@ -257,6 +298,7 @@ BuildCertChain(TrustDomain& trustDomain,
EndEntityOrCA endEntityOrCA,
/*optional*/ KeyUsages requiredKeyUsagesIfPresent,
/*optional*/ SECOidTag requiredEKUIfPresent,
/*optional*/ const SECItem* stapledOCSPResponse,
/*out*/ ScopedCERTCertList& results)
{
PORT_Assert(certToDup);
@ -285,19 +327,12 @@ BuildCertChain(TrustDomain& trustDomain,
rv = BuildForward(trustDomain, cert, time, endEntityOrCA,
requiredKeyUsagesIfPresent, requiredEKUIfPresent,
0, results);
stapledOCSPResponse, 0, results);
if (rv != Success) {
results = nullptr;
return SECFailure;
}
// Build the cert chain even if the cert is expired, because we would
// rather report the untrusted issuer error than the expired error.
if (CheckTimes(cert.GetNSSCert(), time) != Success) {
PR_SetError(SEC_ERROR_EXPIRED_CERTIFICATE, 0);
return SECFailure;
}
return SECSuccess;
}

View File

@ -231,9 +231,13 @@ CheckNameConstraints(BackCert& cert)
PORT_Assert(names);
CERTGeneralName* currentName = const_cast<CERTGeneralName*>(names);
do {
rv = MapSECStatus(CERT_CheckNameSpace(arena, constraints, currentName));
if (rv != Success) {
return rv;
if (CERT_CheckNameSpace(arena, constraints, currentName) != SECSuccess) {
// XXX: It seems like CERT_CheckNameSpace doesn't always call
// PR_SetError when it fails. We set the error code here, though this
// may be papering over some fatal errors. NSS's
// cert_VerifyCertChainOld does something similar.
PR_SetError(SEC_ERROR_CERT_NOT_IN_NAME_SPACE, 0);
return RecoverableError;
}
currentName = CERT_GetNextGeneralName(currentName);
} while (currentName != names);
@ -318,16 +322,42 @@ CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA, const SECItem* encodedEKUs,
return Success;
}
// Checks extensions that apply to both EE and intermediate certs,
// except for AIA, CRL, and AKI/SKI, which are handled elsewhere.
Result
CheckExtensions(BackCert& cert,
EndEntityOrCA endEntityOrCA,
bool isTrustAnchor,
KeyUsages requiredKeyUsagesIfPresent,
SECOidTag requiredEKUIfPresent,
unsigned int subCACount)
CheckIssuerIndependentProperties(TrustDomain& trustDomain,
BackCert& cert,
PRTime time,
EndEntityOrCA endEntityOrCA,
KeyUsages requiredKeyUsagesIfPresent,
SECOidTag requiredEKUIfPresent,
unsigned int subCACount,
/*optional out*/ TrustDomain::TrustLevel* trustLevelOut)
{
Result rv;
TrustDomain::TrustLevel trustLevel;
rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA,
cert.GetNSSCert(),
&trustLevel));
if (rv != Success) {
return rv;
}
if (trustLevel == TrustDomain::ActivelyDistrusted) {
PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
return RecoverableError;
}
if (trustLevel != TrustDomain::TrustAnchor &&
trustLevel != TrustDomain::InheritsTrust) {
// The TrustDomain returned a trust level that we weren't expecting.
PORT_SetError(PR_INVALID_STATE_ERROR);
return FatalError;
}
if (trustLevelOut) {
*trustLevelOut = trustLevel;
}
bool isTrustAnchor = endEntityOrCA == MustBeCA &&
trustLevel == TrustDomain::TrustAnchor;
// 4.2.1.1. Authority Key Identifier dealt with as part of path building
// 4.2.1.2. Subject Key Identifier dealt with as part of path building
@ -336,8 +366,6 @@ CheckExtensions(BackCert& cert,
return FatalError;
}
Result rv;
// 4.2.1.3. Key Usage
rv = CheckKeyUsage(endEntityOrCA, isTrustAnchor, cert.encodedKeyUsage,
@ -373,6 +401,13 @@ CheckExtensions(BackCert& cert,
// 4.2.1.13. CRL Distribution Points will be dealt with elsewhere
// 4.2.1.14. Inhibit anyPolicy
// IMPORTANT: This check must come after the other checks in order for error
// ranking to work correctly.
rv = CheckTimes(cert.GetNSSCert(), time);
if (rv != Success) {
return rv;
}
return Success;
}

View File

@ -23,14 +23,15 @@
namespace insanity { namespace pkix {
Result CheckTimes(const CERTCertificate* cert, PRTime time);
Result CheckExtensions(BackCert& certExt,
EndEntityOrCA endEntityOrCA,
bool isTrustAnchor,
KeyUsages requiredKeyUsagesIfPresent,
SECOidTag requiredEKUIfPresent,
unsigned int depth);
Result CheckIssuerIndependentProperties(
TrustDomain& trustDomain,
BackCert& cert,
PRTime time,
EndEntityOrCA endEntityOrCA,
KeyUsages requiredKeyUsagesIfPresent,
SECOidTag requiredEKUIfPresent,
unsigned int subCACount,
/*optional out*/ TrustDomain::TrustLevel* trustLevel = nullptr);
Result CheckNameConstraints(BackCert& cert);

View File

@ -217,6 +217,13 @@ ExpectTagAndLength(Input& input, uint8_t expectedTag, uint8_t expectedLength)
Result
ExpectTagAndGetLength(Input& input, uint8_t expectedTag, uint16_t& length);
inline Result
ExpectTagAndIgnoreLength(Input& input, uint8_t expectedTag)
{
uint16_t ignored;
return ExpectTagAndGetLength(input, expectedTag, ignored);
}
inline Result
End(Input& input)
{

View File

@ -0,0 +1,949 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* Copyright 2013 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <limits>
#include "insanity/bind.h"
#include "insanity/pkix.h"
#include "pkixcheck.h"
#include "pkixder.h"
#include "hasht.h"
#include "pk11pub.h"
#include "secder.h"
#ifdef _MSC_VER
// C4480: nonstandard extension used: specifying underlying type for enum
#define ENUM_CLASS __pragma(warning(disable: 4480)) enum
#else
#define ENUM_CLASS enum class
#endif
// TODO: use typed/qualified typedefs everywhere?
// TODO: When should we return SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE?
namespace insanity { namespace pkix {
static const PRTime ONE_DAY
= INT64_C(24) * INT64_C(60) * INT64_C(60) * PR_USEC_PER_SEC;
static const PRTime SLOP = ONE_DAY;
// These values correspond to the tag values in the ASN.1 CertStatus
ENUM_CLASS CertStatus : uint8_t {
Good = der::CONTEXT_SPECIFIC | 0,
Revoked = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
Unknown = der::CONTEXT_SPECIFIC | 2
};
class Context
{
public:
Context(TrustDomain& trustDomain,
const CERTCertificate& cert,
CERTCertificate& issuerCert,
PRTime time)
: trustDomain(trustDomain)
, cert(cert)
, issuerCert(issuerCert)
, time(time)
, certStatus(CertStatus::Unknown)
{
}
TrustDomain& trustDomain;
const CERTCertificate& cert;
CERTCertificate& issuerCert;
const PRTime time;
CertStatus certStatus;
private:
Context(const Context&); // delete
void operator=(const Context&); // delete
};
// Verify that potentialSigner is a valid delegated OCSP response signing cert
// according to RFC 6960 section 4.2.2.2.
static Result
CheckOCSPResponseSignerCert(TrustDomain& trustDomain,
CERTCertificate& potentialSigner,
const CERTCertificate& issuerCert, PRTime time)
{
Result rv;
BackCert cert(&potentialSigner, nullptr, BackCert::ExcludeCN);
rv = cert.Init();
if (rv != Success) {
return rv;
}
// We don't need to do a complete verification of the signer (i.e. we don't
// have to call BuildCertChain to verify the entire chain) because we
// already know that the issuerCert is valid, since revocation checking is
// done from the root to the parent after we've built a complete chain that
// we know is otherwise valid. Rather, we just need to do a one-step
// validation from potentialSigner to issuerCert.
//
// It seems reasonable to require the KU_DIGITAL_SIGNATURE key usage on the
// OCSP responder certificate if the OCSP responder certificate has a
// key usage extension. However, according to bug 240456, some OCSP responder
// certificates may have only the nonRepudiation bit set. Also, the OCSP
// specification (RFC 6960) does not mandate any particular key usage to be
// asserted for OCSP responde signers. Oddly, the CABForum Baseline
// Requirements v.1.1.5 do say "If the Root CA Private Key is used for
// signing OCSP responses, then the digitalSignature bit MUST be set."
//
// Note that CheckIssuerIndependentProperties processes
// SEC_OID_OCSP_RESPONDER in the way that the OCSP specification requires us
// to--in particular, it doesn't allow SEC_OID_OCSP_RESPONDER to be implied
// by a missing EKU extension, unlike other EKUs.
//
// TODO(bug 926261): If we're validating for a policy then the policy OID we
// are validating for should be passed to CheckIssuerIndependentProperties.
rv = CheckIssuerIndependentProperties(trustDomain, cert, time,
MustBeEndEntity, 0,
SEC_OID_OCSP_RESPONDER, 0);
if (rv != Success) {
return rv;
}
// It is possible that there exists a certificate with the same key as the
// issuer but with a different name, so we need to compare names
// TODO: needs test
if (!SECITEM_ItemsAreEqual(&cert.GetNSSCert()->derIssuer,
&issuerCert.derSubject) &&
CERT_CompareName(&cert.GetNSSCert()->issuer,
&issuerCert.subject) != SECEqual) {
return Fail(RecoverableError, SEC_ERROR_OCSP_RESPONDER_CERT_INVALID);
}
// TODO(bug 926260): check name constraints
if (trustDomain.VerifySignedData(&potentialSigner.signatureWrap,
&issuerCert) != SECSuccess) {
return MapSECStatus(SECFailure);
}
// TODO: check for revocation of the OCSP responder certificate unless no-check
// or the caller forcing no-check. To properly support the no-check policy, we'd
// need to enforce policy constraints from the issuerChain.
return Success;
}
//typedef enum {
// ocspResponderID_byName = 1,
// ocspResponderID_byKey = 2
//} ResponderIDType;
ENUM_CLASS ResponderIDType : uint8_t
{
byName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
byKey = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 2
};
static inline der::Result OCSPResponse(der::Input&, Context&);
static inline der::Result ResponseBytes(der::Input&, Context&);
static inline der::Result BasicResponse(der::Input&, Context&);
static inline der::Result ResponseData(
der::Input& tbsResponseData, Context& context,
const CERTSignedData& signedResponseData,
/*const*/ SECItem* certs, size_t numCerts);
static inline der::Result SingleResponse(der::Input& input,
Context& context);
static inline der::Result CheckExtensionsForCriticality(der::Input&);
static inline der::Result CertID(der::Input& input,
const Context& context,
/*out*/ bool& match);
static der::Result MatchIssuerKey(const SECItem& issuerKeyHash,
const CERTCertificate& issuer,
/*out*/ bool& match);
// RFC 6960 section 4.2.2.2: The OCSP responder must either be the issuer of
// the cert or it must be a delegated OCSP response signing cert directly
// issued by the issuer. If the OCSP responder is a delegated OCSP response
// signer, then its certificate is (probably) embedded within the OCSP
// response and we'll need to verify that it is a valid certificate that chains
// *directly* to issuerCert.
static CERTCertificate*
GetOCSPSignerCertificate(TrustDomain& trustDomain,
ResponderIDType responderIDType,
const SECItem& responderIDItem,
const SECItem* certs, size_t numCerts,
CERTCertificate& issuerCert, PRTime time)
{
bool isIssuer = true;
size_t i = 0;
for (;;) {
ScopedCERTCertificate potentialSigner;
if (isIssuer) {
potentialSigner = CERT_DupCertificate(&issuerCert);
} else if (i < numCerts) {
potentialSigner = CERT_NewTempCertificate(
CERT_GetDefaultCertDB(),
/*TODO*/const_cast<SECItem*>(&certs[i]), nullptr,
false, false);
if (!potentialSigner) {
return nullptr;
}
++i;
} else {
PR_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT, 0);
return nullptr;
}
bool match;
switch (responderIDType) {
case ResponderIDType::byName:
// The CA is very likely to have encoded the name in the OCSP response
// exactly the same as the name is encoded in the signing certificate.
// Consequently, most of the time we will avoid parsing the name
// completely. We're assuming here that the signer's subject name is
// correctly formatted.
// TODO: need test for exact name
// TODO: need test for non-exact name match
match = SECITEM_ItemsAreEqual(&responderIDItem,
&potentialSigner->derSubject);
if (!match) {
ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
if (!arena) {
return nullptr;
}
CERTName name;
if (SEC_QuickDERDecodeItem(arena.get(), &name,
SEC_ASN1_GET(CERT_NameTemplate),
&responderIDItem) != SECSuccess) {
return nullptr;
}
match = CERT_CompareName(&name, &potentialSigner->subject) == SECEqual;
}
break;
case ResponderIDType::byKey:
{
der::Input responderID;
if (responderID.Init(responderIDItem.data, responderIDItem.len)
!= der::Success) {
return nullptr;
}
SECItem issuerKeyHash;
if (der::Skip(responderID, der::OCTET_STRING, issuerKeyHash) != der::Success) {
return nullptr;
}
if (MatchIssuerKey(issuerKeyHash, *potentialSigner.get(), match)
!= der::Success) {
return nullptr;
}
break;
}
default:
PR_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE, 0);
return nullptr;
}
if (match && !isIssuer) {
Result rv = CheckOCSPResponseSignerCert(trustDomain,
*potentialSigner.get(),
issuerCert, time);
if (rv == RecoverableError) {
match = false;
} else if (rv != Success) {
return nullptr;
}
}
if (match) {
return potentialSigner.release();
}
isIssuer = false;
}
}
static SECStatus
VerifySignature(Context& context, ResponderIDType responderIDType,
const SECItem& responderID, const SECItem* certs,
size_t numCerts, const CERTSignedData& signedResponseData)
{
ScopedCERTCertificate signer(
GetOCSPSignerCertificate(context.trustDomain, responderIDType, responderID,
certs, numCerts, context.issuerCert,
context.time));
if (!signer) {
return SECFailure;
}
if (context.trustDomain.VerifySignedData(&signedResponseData, signer.get())
!= SECSuccess) {
if (PR_GetError() == SEC_ERROR_BAD_SIGNATURE) {
PR_SetError(SEC_ERROR_OCSP_BAD_SIGNATURE, 0);
}
return SECFailure;
}
return SECSuccess;
}
SECStatus
VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
const CERTCertificate* cert,
CERTCertificate* issuerCert, PRTime time,
const SECItem* encodedResponse)
{
PR_ASSERT(cert);
PR_ASSERT(issuerCert);
// TODO: PR_Assert(pinArg)
PR_ASSERT(encodedResponse);
if (!cert || !issuerCert || !encodedResponse || !encodedResponse->data) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return SECFailure;
}
der::Input input;
if (input.Init(encodedResponse->data, encodedResponse->len) != der::Success) {
return SECFailure;
}
Context context(trustDomain, *cert, *issuerCert, time);
if (der::Nested(input, der::SEQUENCE,
bind(OCSPResponse, _1, ref(context))) != der::Success) {
return SECFailure;
}
if (der::End(input) != der::Success) {
return SECFailure;
}
switch (context.certStatus) {
case CertStatus::Good:
return SECSuccess;
case CertStatus::Revoked:
PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0);
return SECFailure;
case CertStatus::Unknown:
PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
return SECFailure;
}
PR_NOT_REACHED("unknown CertStatus");
PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
return SECFailure;
}
// OCSPResponse ::= SEQUENCE {
// responseStatus OCSPResponseStatus,
// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
//
static inline der::Result
OCSPResponse(der::Input& input, Context& context)
{
// OCSPResponseStatus ::= ENUMERATED {
// successful (0), -- Response has valid confirmations
// malformedRequest (1), -- Illegal confirmation request
// internalError (2), -- Internal error in issuer
// tryLater (3), -- Try again later
// -- (4) is not used
// sigRequired (5), -- Must sign the request
// unauthorized (6) -- Request unauthorized
// }
uint8_t responseStatus;
if (der::Enumerated(input, responseStatus) != der::Success) {
return der::Failure;
}
switch (responseStatus) {
case 0: break; // successful
case 1: return der::Fail(SEC_ERROR_OCSP_MALFORMED_REQUEST);
case 2: return der::Fail(SEC_ERROR_OCSP_SERVER_ERROR);
case 3: return der::Fail(SEC_ERROR_OCSP_TRY_SERVER_LATER);
case 5: return der::Fail(SEC_ERROR_OCSP_REQUEST_NEEDS_SIG);
case 6: return der::Fail(SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST);
default: return der::Fail(SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS);
}
return der::Nested(input, der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
der::SEQUENCE, bind(ResponseBytes, _1, ref(context)));
}
// ResponseBytes ::= SEQUENCE {
// responseType OBJECT IDENTIFIER,
// response OCTET STRING }
static inline der::Result
ResponseBytes(der::Input& input, Context& context)
{
static const uint8_t id_pkix_ocsp_basic[] = {
0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
};
if (der::OID(input, id_pkix_ocsp_basic) != der::Success) {
return der::Failure;
}
return der::Nested(input, der::OCTET_STRING, der::SEQUENCE,
bind(BasicResponse, _1, ref(context)));
}
// BasicOCSPResponse ::= SEQUENCE {
// tbsResponseData ResponseData,
// signatureAlgorithm AlgorithmIdentifier,
// signature BIT STRING,
// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
der::Result
BasicResponse(der::Input& input, Context& context)
{
der::Input::Mark mark(input.GetMark());
uint16_t length;
if (der::ExpectTagAndGetLength(input, der::SEQUENCE, length)
!= der::Success) {
return der::Failure;
}
// The signature covers the entire DER encoding of tbsResponseData, including
// the beginning tag and length. However, when we're parsing tbsResponseData,
// we want to strip off the tag and length because we don't need it after
// we've confirmed it's there and figured out what length it is.
der::Input tbsResponseData;
if (input.Skip(length, tbsResponseData) != der::Success) {
return der::Failure;
}
CERTSignedData signedData;
input.GetSECItem(siBuffer, mark, signedData.data);
if (der::Nested(input, der::SEQUENCE,
bind(der::AlgorithmIdentifier, _1,
ref(signedData.signatureAlgorithm))) != der::Success) {
return der::Failure;
}
if (der::Skip(input, der::BIT_STRING, signedData.signature) != der::Success) {
return der::Failure;
}
if (signedData.signature.len == 0) {
return der::Fail(SEC_ERROR_OCSP_BAD_SIGNATURE);
}
unsigned int unusedBitsAtEnd = signedData.signature.data[0];
// XXX: Really the constraint should be that unusedBitsAtEnd must be less
// than 7. But, we suspect there are no valid OCSP response signatures with
// non-zero unused bits. It seems like NSS assumes this in various places, so
// we enforce it. If we find compatibility issues, we'll know we're wrong.
if (unusedBitsAtEnd != 0) {
return der::Fail(SEC_ERROR_OCSP_BAD_SIGNATURE);
}
++signedData.signature.data;
--signedData.signature.len;
signedData.signature.len = (signedData.signature.len << 3); // Bytes to bits
// Parse certificates, if any
SECItem certs[8];
size_t numCerts = 0;
if (!input.AtEnd()) {
// We ignore the lengths of the wrappers because we'll detect bad lengths
// during parsing--too short and we'll run out of input for parsing a cert,
// and too long and we'll have leftover data that won't parse as a cert.
// [0] wrapper
if (der::ExpectTagAndIgnoreLength(
input, der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0)
!= der::Success) {
return der::Failure;
}
// SEQUENCE wrapper
if (der::ExpectTagAndIgnoreLength(input, der::SEQUENCE) != der::Success) {
return der::Failure;
}
// sequence of certificates
while (!input.AtEnd()) {
if (numCerts == PR_ARRAY_SIZE(certs)) {
return der::Fail(SEC_ERROR_BAD_DER);
}
// Unwrap the SEQUENCE that contains the certificate, which is itself a
// SEQUENCE.
der::Input::Mark mark(input.GetMark());
if (der::Skip(input, der::SEQUENCE) != der::Success) {
return der::Failure;
}
input.GetSECItem(siBuffer, mark, certs[numCerts]);
++numCerts;
}
}
return ResponseData(tbsResponseData, context, signedData, certs, numCerts);
}
// ResponseData ::= SEQUENCE {
// version [0] EXPLICIT Version DEFAULT v1,
// responderID ResponderID,
// producedAt GeneralizedTime,
// responses SEQUENCE OF SingleResponse,
// responseExtensions [1] EXPLICIT Extensions OPTIONAL }
static inline der::Result
ResponseData(der::Input& input, Context& context,
const CERTSignedData& signedResponseData,
/*const*/ SECItem* certs, size_t numCerts)
{
uint8_t version;
if (der::OptionalVersion(input, version) != der::Success) {
return der::Failure;
}
if (version != der::v1) {
// TODO: more specific error code for bad version?
return der::Fail(SEC_ERROR_BAD_DER);
}
// ResponderID ::= CHOICE {
// byName [1] Name,
// byKey [2] KeyHash }
SECItem responderID;
uint16_t responderIDLength;
ResponderIDType responderIDType
= input.Peek(static_cast<uint8_t>(ResponderIDType::byName))
? ResponderIDType::byName
: ResponderIDType::byKey;
if (ExpectTagAndGetLength(input, static_cast<uint8_t>(responderIDType),
responderIDLength) != der::Success) {
return der::Failure;
}
// TODO: responderID probably needs to have another level of ASN1 tag/length
// checked and stripped.
if (input.Skip(responderIDLength, responderID) != der::Success) {
return der::Failure;
}
// This is the soonest we can verify the signature. We verify the signature
// right away to follow the principal of minimizing the processing of data
// before verifying its signature.
if (VerifySignature(context, responderIDType, responderID, certs, numCerts,
signedResponseData) != SECSuccess) {
return der::Failure;
}
// TODO: Do we even need to parse this? Should we just skip it?
PRTime producedAt;
if (der::GeneralizedTime(input, producedAt) != der::Success) {
return der::Failure;
}
// We don't accept an empty sequence of responses. In practice, a legit OCSP
// responder will never return an empty response, and handling the case of an
// empty response makes things unnecessarily complicated.
if (der::NestedOf(input, der::SEQUENCE, der::SEQUENCE,
der::MustNotBeEmpty,
bind(SingleResponse, _1, ref(context))) != der::Success) {
return der::Failure;
}
if (!input.AtEnd()) {
if (CheckExtensionsForCriticality(input) != der::Success) {
return der::Failure;
}
}
return der::Success;
}
// SingleResponse ::= SEQUENCE {
// certID CertID,
// certStatus CertStatus,
// thisUpdate GeneralizedTime,
// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
// singleExtensions [1] EXPLICIT Extensions{{re-ocsp-crl |
// re-ocsp-archive-cutoff |
// CrlEntryExtensions, ...}
// } OPTIONAL }
static inline der::Result
SingleResponse(der::Input& input, Context& context)
{
bool match = false;
if (der::Nested(input, der::SEQUENCE,
bind(CertID, _1, cref(context), ref(match)))
!= der::Success) {
return der::Failure;
}
if (!match) {
return der::Success;
}
// CertStatus ::= CHOICE {
// good [0] IMPLICIT NULL,
// revoked [1] IMPLICIT RevokedInfo,
// unknown [2] IMPLICIT UnknownInfo }
//
// In the event of multiple SingleResponses for a cert that have conflicting
// statuses, we use the following precedence rules:
//
// * revoked overrides good and unknown
// * good overrides unknown
if (input.Peek(static_cast<uint8_t>(CertStatus::Good))) {
if (ExpectTagAndLength(input, static_cast<uint8_t>(CertStatus::Good), 0)
!= der::Success) {
return der::Failure;
}
if (context.certStatus != CertStatus::Revoked) {
context.certStatus = CertStatus::Good;
}
} else if (input.Peek(static_cast<uint8_t>(CertStatus::Revoked))) {
// We don't need any info from the RevokedInfo structure, so we don't even
// parse it. TODO: We should mention issues like this in the explanation of
// why we treat invalid OCSP responses equivalently to revoked for OCSP
// stapling.
if (der::Skip(input, static_cast<uint8_t>(CertStatus::Revoked))
!= der::Success) {
return der::Failure;
}
context.certStatus = CertStatus::Revoked;
} else if (ExpectTagAndLength(input,
static_cast<uint8_t>(CertStatus::Unknown),
0) != der::Success) {
return der::Failure;
}
// http://tools.ietf.org/html/rfc6960#section-3.2
// 5. The time at which the status being indicated is known to be
// correct (thisUpdate) is sufficiently recent;
// 6. When available, the time at or before which newer information will
// be available about the status of the certificate (nextUpdate) is
// greater than the current time.
// We won't accept any OCSP responses that are more than 10 days old, even if
// the nextUpdate time is further in the future.
static const PRTime OLDEST_ACCEPTABLE = INT64_C(10) * ONE_DAY;
PRTime thisUpdate;
if (der::GeneralizedTime(input, thisUpdate) != der::Success) {
return der::Failure;
}
if (thisUpdate > context.time + SLOP) {
return der::Fail(SEC_ERROR_OCSP_FUTURE_RESPONSE);
}
PRTime notAfter;
static const uint8_t NEXT_UPDATE_TAG =
der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0;
if (input.Peek(NEXT_UPDATE_TAG)) {
PRTime nextUpdate;
if (der::Nested(input, NEXT_UPDATE_TAG,
bind(der::GeneralizedTime, _1, ref(nextUpdate)))
!= der::Success) {
return der::Failure;
}
if (nextUpdate < thisUpdate) {
return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
}
if (nextUpdate - thisUpdate <= OLDEST_ACCEPTABLE) {
notAfter = nextUpdate;
} else {
notAfter = thisUpdate + OLDEST_ACCEPTABLE;
}
} else {
// NSS requires all OCSP responses without a nextUpdate to be recent.
// Match that stricter behavior.
notAfter = thisUpdate + ONE_DAY;
}
if (context.time < SLOP) { // prevent underflow
return der::Fail(SEC_ERROR_INVALID_ARGS);
}
if (context.time - SLOP > notAfter) {
return der::Fail(SEC_ERROR_OCSP_OLD_RESPONSE);
}
if (!input.AtEnd()) {
if (CheckExtensionsForCriticality(input) != der::Success) {
return der::Failure;
}
}
return der::Success;
}
// CertID ::= SEQUENCE {
// hashAlgorithm AlgorithmIdentifier,
// issuerNameHash OCTET STRING, -- Hash of issuer's DN
// issuerKeyHash OCTET STRING, -- Hash of issuer's public key
// serialNumber CertificateSerialNumber }
static inline der::Result
CertID(der::Input& input, const Context& context, /*out*/ bool& match)
{
match = false;
SECAlgorithmID hashAlgorithm;
if (der::Nested(input, der::SEQUENCE,
bind(der::AlgorithmIdentifier, _1, ref(hashAlgorithm)))
!= der::Success) {
return der::Failure;
}
SECItem issuerNameHash;
if (der::Skip(input, der::OCTET_STRING, issuerNameHash) != der::Success) {
return der::Failure;
}
SECItem issuerKeyHash;
if (der::Skip(input, der::OCTET_STRING, issuerKeyHash) != der::Success) {
return der::Failure;
}
SECItem serialNumber;
if (der::CertificateSerialNumber(input, serialNumber) != der::Success) {
return der::Failure;
}
const CERTCertificate& cert = context.cert;
const CERTCertificate& issuerCert = context.issuerCert;
if (!SECITEM_ItemsAreEqual(&serialNumber, &cert.serialNumber)) {
return der::Success;
}
// TODO: support SHA-2 hashes.
SECOidTag hashAlg = SECOID_GetAlgorithmTag(&hashAlgorithm);
if (hashAlg != SEC_OID_SHA1) {
return der::Success;
}
if (issuerNameHash.len != SHA1_LENGTH) {
return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
}
// From http://tools.ietf.org/html/rfc6960#section-4.1.1:
// "The hash shall be calculated over the DER encoding of the
// issuer's name field in the certificate being checked."
uint8_t hashBuf[SHA1_LENGTH];
if (PK11_HashBuf(SEC_OID_SHA1, hashBuf, cert.derIssuer.data,
cert.derIssuer.len) != SECSuccess) {
return der::Failure;
}
if (memcmp(hashBuf, issuerNameHash.data, issuerNameHash.len)) {
return der::Success;
}
return MatchIssuerKey(issuerKeyHash, issuerCert, match);
}
// From http://tools.ietf.org/html/rfc6960#section-4.1.1:
// "The hash shall be calculated over the value (excluding tag and length) of
// the subject public key field in the issuer's certificate."
static der::Result
MatchIssuerKey(const SECItem& issuerKeyHash, const CERTCertificate& issuer,
/*out*/ bool& match)
{
if (issuerKeyHash.len != SHA1_LENGTH) {
return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
}
// TODO(bug 966856): support SHA-2 hashes
// Copy just the length and data pointer (nothing needs to be freed) of the
// subject public key so we can convert the length from bits to bytes, which
// is what the digest function expects.
SECItem spk = issuer.subjectPublicKeyInfo.subjectPublicKey;
DER_ConvertBitString(&spk);
static uint8_t hashBuf[SHA1_LENGTH];
if (PK11_HashBuf(SEC_OID_SHA1, hashBuf, spk.data, spk.len) != SECSuccess) {
return der::Failure;
}
match = !memcmp(hashBuf, issuerKeyHash.data, issuerKeyHash.len);
return der::Success;
}
// Extension ::= SEQUENCE {
// extnID OBJECT IDENTIFIER,
// critical BOOLEAN DEFAULT FALSE,
// extnValue OCTET STRING
// }
static der::Result
CheckExtensionForCriticality(der::Input& input)
{
uint16_t toSkip;
if (ExpectTagAndGetLength(input, der::OIDTag, toSkip) != der::Success) {
return der::Failure;
}
// TODO: maybe we should check the syntax of the OID value
if (input.Skip(toSkip) != der::Success) {
return der::Failure;
}
// The only valid explicit encoding of the value is TRUE, so don't even
// bother parsing it, since we're going to fail either way.
if (input.Peek(der::BOOLEAN)) {
return der::Fail(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION);
}
if (ExpectTagAndGetLength(input, der::OCTET_STRING, toSkip)
!= der::Success) {
return der::Failure;
}
return input.Skip(toSkip);
}
static der::Result
CheckExtensionsForCriticality(der::Input& input)
{
return der::NestedOf(input, der::SEQUENCE | 1, der::SEQUENCE,
der::MustNotBeEmpty, CheckExtensionForCriticality);
}
// 1. The certificate identified in a received response corresponds to
// the certificate that was identified in the corresponding request;
// 2. The signature on the response is valid;
// 3. The identity of the signer matches the intended recipient of the
// request;
// 4. The signer is currently authorized to provide a response for the
// certificate in question;
// 5. The time at which the status being indicated is known to be
// correct (thisUpdate) is sufficiently recent;
// 6. When available, the time at or before which newer information will
// be available about the status of the certificate (nextUpdate) is
// greater than the current time.
//
// Responses whose nextUpdate value is earlier than
// the local system time value SHOULD be considered unreliable.
// Responses whose thisUpdate time is later than the local system time
// SHOULD be considered unreliable.
//
// If nextUpdate is not set, the responder is indicating that newer
// revocation information is available all the time.
//
// http://tools.ietf.org/html/rfc5019#section-4
SECItem*
CreateEncodedOCSPRequest(PLArenaPool* arena,
const CERTCertificate* cert,
const CERTCertificate* issuerCert)
{
if (!arena || !cert || !issuerCert) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return nullptr;
}
// We do not add any extensions to the request.
// RFC 6960 says "An OCSP client MAY wish to specify the kinds of response
// types it understands. To do so, it SHOULD use an extension with the OID
// id-pkix-ocsp-response." This use of MAY and SHOULD is unclear. MSIE11
// on Windows 8.1 does not include any extensions, whereas NSS has always
// included the id-pkix-ocsp-response extension. Avoiding the sending the
// extension is better for OCSP GET because it makes the request smaller,
// and thus more likely to fit within the 255 byte limit for OCSP GET that
// is specified in RFC 5019 Section 5.
// Bug 966856: Add the id-pkix-ocsp-pref-sig-algs extension.
// Since we don't know whether the OCSP responder supports anything other
// than SHA-1, we have no choice but to use SHA-1 for issuerNameHash and
// issuerKeyHash.
static const uint8_t hashAlgorithm[11] = {
0x30, 0x09, // SEQUENCE
0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, // OBJECT IDENTIFIER id-sha1
0x05, 0x00, // NULL
};
static const uint8_t hashLen = SHA1_LENGTH;
static const unsigned int totalLenWithoutSerialNumberData
= 2 // OCSPRequest
+ 2 // tbsRequest
+ 2 // requestList
+ 2 // Request
+ 2 // reqCert (CertID)
+ PR_ARRAY_SIZE(hashAlgorithm) // hashAlgorithm
+ 2 + hashLen // issuerNameHash
+ 2 + hashLen // issuerKeyHash
+ 2; // serialNumber (header)
// The only way we could have a request this large is if the serialNumber was
// ridiculously and unreasonably large. RFC 5280 says "Conforming CAs MUST
// NOT use serialNumber values longer than 20 octets." With this restriction,
// we allow for some amount of non-conformance with that requirement while
// still ensuring we can encode the length values in the ASN.1 TLV structures
// in a single byte.
if (issuerCert->serialNumber.len > 127u - totalLenWithoutSerialNumberData) {
PR_SetError(SEC_ERROR_BAD_DATA, 0);
return nullptr;
}
uint8_t totalLen = static_cast<uint8_t>(totalLenWithoutSerialNumberData +
cert->serialNumber.len);
SECItem* encodedRequest = SECITEM_AllocItem(arena, nullptr, totalLen);
if (!encodedRequest) {
return nullptr;
}
uint8_t* d = encodedRequest->data;
*d++ = 0x30; *d++ = totalLen - 2; // OCSPRequest (SEQUENCE)
*d++ = 0x30; *d++ = totalLen - 4; // tbsRequest (SEQUENCE)
*d++ = 0x30; *d++ = totalLen - 6; // requestList (SEQUENCE OF)
*d++ = 0x30; *d++ = totalLen - 8; // Request (SEQUENCE)
*d++ = 0x30; *d++ = totalLen - 10; // reqCert (CertID SEQUENCE)
// reqCert.hashAlgorithm
for (size_t i = 0; i < PR_ARRAY_SIZE(hashAlgorithm); ++i) {
*d++ = hashAlgorithm[i];
}
// reqCert.issuerNameHash (OCTET STRING)
*d++ = 0x04;
*d++ = hashLen;
if (PK11_HashBuf(SEC_OID_SHA1, d, issuerCert->derSubject.data,
issuerCert->derSubject.len) != SECSuccess) {
return nullptr;
}
d += hashLen;
// reqCert.issuerKeyHash (OCTET STRING)
*d++ = 0x04;
*d++ = hashLen;
SECItem key = issuerCert->subjectPublicKeyInfo.subjectPublicKey;
DER_ConvertBitString(&key);
if (PK11_HashBuf(SEC_OID_SHA1, d, key.data, key.len) != SECSuccess) {
return nullptr;
}
d += hashLen;
// reqCert.serialNumber (INTEGER)
*d++ = 0x02; // INTEGER
*d++ = static_cast<uint8_t>(cert->serialNumber.len);
for (size_t i = 0; i < cert->serialNumber.len; ++i) {
*d++ = cert->serialNumber.data[i];
}
PR_ASSERT(d == encodedRequest->data + totalLen);
return encodedRequest;
}
} } // namespace insanity::pkix

View File

@ -109,7 +109,12 @@ public:
BackCert* const childCert;
const CERTCertificate* GetNSSCert() const { return nssCert; }
// Only non-const so that we can pass this to TrustDomain::IsRevoked,
// which only takes a non-const pointer because VerifyEncodedOCSPResponse
// requires it, and that is only because the implementation of
// VerifyEncodedOCSPResponse does a CERT_DupCertificate. TODO: get rid
// of that CERT_DupCertificate so that we can make all these things const.
/*const*/ CERTCertificate* GetNSSCert() const { return nssCert; }
// Returns the names that should be considered when evaluating name
// constraints. The list is constructed lazily and cached. The result is a

View File

@ -114,64 +114,119 @@ function run_test() {
// setup and start ocsp responder
Services.prefs.setCharPref("network.dns.localDomains",
'www.example.com, crl.example.com');
add_tests_in_mode(true);
add_tests_in_mode(false);
run_next_test();
}
add_test(function() {
clearOCSPCache();
let ocspResponder = start_ocsp_responder(
isDebugBuild ? ["int-ev-valid", "ev-valid"]
: ["ev-valid"]);
check_ee_for_ev("ev-valid", isDebugBuild);
ocspResponder.stop(run_next_test);
});
function add_tests_in_mode(useInsanity)
{
add_test(function () {
Services.prefs.setBoolPref("security.use_insanity_verification",
useInsanity);
run_next_test();
});
add_test(function() {
clearOCSPCache();
let ocspResponder = start_ocsp_responder(["non-ev-root"]);
check_ee_for_ev("non-ev-root", false);
ocspResponder.stop(run_next_test);
});
add_test(function () {
clearOCSPCache();
let ocspResponder = start_ocsp_responder(
isDebugBuild ? ["int-ev-valid", "ev-valid"]
: ["ev-valid"]);
check_ee_for_ev("ev-valid", isDebugBuild);
ocspResponder.stop(run_next_test);
});
add_test(function() {
clearOCSPCache();
// libpkix will attempt to validate the intermediate, which does have an
// OCSP URL.
let ocspResponder = isDebugBuild ? start_ocsp_responder(["int-ev-valid"])
: failingOCSPResponder();
check_ee_for_ev("no-ocsp-url-cert", false);
ocspResponder.stop(run_next_test);
});
add_test(function() {
clearOCSPCache();
let ocspResponder = start_ocsp_responder(["non-ev-root"]);
check_ee_for_ev("non-ev-root", false);
ocspResponder.stop(run_next_test);
});
// bug 917380: Chcek that an untrusted EV root is untrusted.
const nsIX509Cert = Ci.nsIX509Cert;
add_test(function() {
let evRootCA = certdb.findCertByNickname(null, evrootnick);
certdb.setCertTrust(evRootCA, nsIX509Cert.CA_CERT, 0);
add_test(function() {
clearOCSPCache();
// libpkix will attempt to validate the intermediate, which does have an
// OCSP URL.
let ocspResponder = isDebugBuild ? start_ocsp_responder(["int-ev-valid"])
: failingOCSPResponder();
check_ee_for_ev("no-ocsp-url-cert", false);
ocspResponder.stop(run_next_test);
});
clearOCSPCache();
let ocspResponder = failingOCSPResponder();
check_cert_err("ev-valid", SEC_ERROR_UNTRUSTED_ISSUER);
ocspResponder.stop(run_next_test);
});
// bug 917380: Chcek that an untrusted EV root is untrusted.
const nsIX509Cert = Ci.nsIX509Cert;
add_test(function() {
let evRootCA = certdb.findCertByNickname(null, evrootnick);
certdb.setCertTrust(evRootCA, nsIX509Cert.CA_CERT, 0);
// bug 917380: Chcek that a trusted EV root is trusted after disabling and
// re-enabling trust.
add_test(function() {
let evRootCA = certdb.findCertByNickname(null, evrootnick);
certdb.setCertTrust(evRootCA, nsIX509Cert.CA_CERT,
Ci.nsIX509CertDB.TRUSTED_SSL |
Ci.nsIX509CertDB.TRUSTED_EMAIL |
Ci.nsIX509CertDB.TRUSTED_OBJSIGN);
clearOCSPCache();
let ocspResponder = failingOCSPResponder();
check_cert_err("ev-valid",
useInsanity ? SEC_ERROR_UNKNOWN_ISSUER
: SEC_ERROR_UNTRUSTED_ISSUER);
ocspResponder.stop(run_next_test);
});
clearOCSPCache();
let ocspResponder = start_ocsp_responder(
isDebugBuild ? ["int-ev-valid", "ev-valid"]
: ["ev-valid"]);
check_ee_for_ev("ev-valid", isDebugBuild);
ocspResponder.stop(run_next_test);
});
// bug 917380: Chcek that a trusted EV root is trusted after disabling and
// re-enabling trust.
add_test(function() {
let evRootCA = certdb.findCertByNickname(null, evrootnick);
certdb.setCertTrust(evRootCA, nsIX509Cert.CA_CERT,
Ci.nsIX509CertDB.TRUSTED_SSL |
Ci.nsIX509CertDB.TRUSTED_EMAIL |
Ci.nsIX509CertDB.TRUSTED_OBJSIGN);
clearOCSPCache();
let ocspResponder = start_ocsp_responder(
isDebugBuild ? ["int-ev-valid", "ev-valid"]
: ["ev-valid"]);
check_ee_for_ev("ev-valid", isDebugBuild);
ocspResponder.stop(run_next_test);
});
add_test(function () {
check_no_ocsp_requests("ev-valid",
isDebugBuild ? SEC_ERROR_REVOKED_CERTIFICATE
: SEC_ERROR_EXTENSION_NOT_FOUND);
});
add_test(function () {
check_no_ocsp_requests("non-ev-root",
isDebugBuild ? SEC_ERROR_UNTRUSTED_ISSUER
: SEC_ERROR_EXTENSION_NOT_FOUND);
});
add_test(function () {
check_no_ocsp_requests("no-ocsp-url-cert",
isDebugBuild ? SEC_ERROR_REVOKED_CERTIFICATE
: SEC_ERROR_EXTENSION_NOT_FOUND);
});
// Test the EV continues to work with flags after successful EV verification
add_test(function () {
clearOCSPCache();
let ocspResponder = start_ocsp_responder(
isDebugBuild ? ["int-ev-valid", "ev-valid"]
: ["ev-valid"]);
check_ee_for_ev("ev-valid", isDebugBuild);
ocspResponder.stop(function () {
// without net it must be able to EV verify
let failingOcspResponder = failingOCSPResponder();
let cert = certdb.findCertByNickname(null, "ev-valid");
let hasEVPolicy = {};
let verifiedChain = {};
let flags = Ci.nsIX509CertDB.FLAG_LOCAL_ONLY |
Ci.nsIX509CertDB.FLAG_MUST_BE_EV;
let error = certdb.verifyCertNow(cert, certificateUsageSSLServer,
flags, verifiedChain, hasEVPolicy);
do_check_eq(hasEVPolicy.value, isDebugBuild);
do_check_eq(error, isDebugBuild ? 0 : SEC_ERROR_EXTENSION_NOT_FOUND);
failingOcspResponder.stop(run_next_test);
});
});
}
// bug 950240: add FLAG_MUST_BE_EV to CertVerifier::VerifyCert
// to prevent spurious OCSP requests that race with OCSP stapling.
@ -198,49 +253,3 @@ function check_no_ocsp_requests(cert_name, expected_error) {
do_check_eq(identityInfo.isExtendedValidation, false);
ocspResponder.stop(run_next_test);
}
add_test(function() {
check_no_ocsp_requests("ev-valid", isDebugBuild ?
SEC_ERROR_REVOKED_CERTIFICATE:
SEC_ERROR_EXTENSION_NOT_FOUND);
});
add_test(function() {
check_no_ocsp_requests("non-ev-root", isDebugBuild ?
SEC_ERROR_UNTRUSTED_ISSUER:
SEC_ERROR_EXTENSION_NOT_FOUND);
});
add_test(function() {
check_no_ocsp_requests("no-ocsp-url-cert", isDebugBuild?
SEC_ERROR_REVOKED_CERTIFICATE:
SEC_ERROR_EXTENSION_NOT_FOUND);
});
// Test the EV continues to work with flags after successful EV verification
add_test(function() {
clearOCSPCache();
let ocspResponder = start_ocsp_responder(
isDebugBuild ? ["int-ev-valid", "ev-valid"]
: ["ev-valid"]);
check_ee_for_ev("ev-valid", isDebugBuild);
ocspResponder.stop(function () {
// without net it must be able to EV verify
let failingOcspResponder = failingOCSPResponder();
let cert = certdb.findCertByNickname(null, "ev-valid");
let hasEVPolicy = {};
let verifiedChain = {};
let flags = Ci.nsIX509CertDB.FLAG_LOCAL_ONLY |
Ci.nsIX509CertDB.FLAG_MUST_BE_EV;
let error = certdb.verifyCertNow(cert, certificateUsageSSLServer,
flags, verifiedChain, hasEVPolicy);
do_check_eq(hasEVPolicy.value, isDebugBuild);
do_check_eq(error, isDebugBuild ? 0 : SEC_ERROR_EXTENSION_NOT_FOUND);
failingOcspResponder.stop(run_next_test);
});
});

View File

@ -107,7 +107,7 @@ function run_test() {
//---------------------------------------------------------------------------
add_test(function() { ocspResponder.stop(run_next_test); run_next_test(); });
add_test(function() { ocspResponder.stop(run_next_test); });
run_next_test();
}

View File

@ -9,27 +9,45 @@
// response with a bad signature. With security.OCSP.require set to true,
// this should fail (but it also shouldn't cause assertion failures).
let gOCSPRequestCount = 0;
function run_test() {
do_get_profile();
Services.prefs.setBoolPref("security.OCSP.require", true);
let args = [ ["bad-signature", "localhostAndExampleCom", "unused" ] ];
let ocspResponses = generateOCSPResponses(args, "tlsserver");
let ocspResponseBadSignature = ocspResponses[0];
let ocspRequestCount = 0;
let ocspResponder = new HttpServer();
ocspResponder.registerPrefixHandler("/", function(request, response) {
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Content-Type", "application/ocsp-response");
response.write(ocspResponseBadSignature);
ocspRequestCount++;
});
ocspResponder.start(8080);
// We don't actually make use of stapling in this test. This is just how we
// get a TLS connection.
add_tls_server_setup("OCSPStaplingServer");
let args = [["bad-signature", "localhostAndExampleCom", "unused"]];
let ocspResponses = generateOCSPResponses(args, "tlsserver");
let ocspResponseBadSignature = ocspResponses[0];
let ocspResponder = new HttpServer();
ocspResponder.registerPrefixHandler("/", function (request, response) {
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Content-Type", "application/ocsp-response");
response.write(ocspResponseBadSignature);
gOCSPRequestCount++;
});
ocspResponder.start(8080);
add_tests_in_mode(true);
add_tests_in_mode(false);
add_test(function () { ocspResponder.stop(run_next_test); });
run_next_test();
}
function add_tests_in_mode(useInsanity)
{
add_test(function () {
Services.prefs.setBoolPref("security.use_insanity_verification",
useInsanity);
run_next_test();
});
add_connection_test("ocsp-stapling-none.example.com",
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_INVALID_SIGNING_CERT));
// bug 964493 - using a cached OCSP response with a bad signature would cause
@ -38,7 +56,11 @@ function run_test() {
// of the error handling code.
add_connection_test("ocsp-stapling-none.example.com",
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_INVALID_SIGNING_CERT));
add_test(function() { ocspResponder.stop(run_next_test); });
add_test(function() { do_check_eq(ocspRequestCount, 1); run_next_test(); });
run_next_test();
add_test(function () {
// XXX(bug 915932): special case for insanity::pkix due to the temporary
// lack of an OCSP cache.
do_check_eq(gOCSPRequestCount, useInsanity ? 2 : 1);
gOCSPRequestCount = 0;
run_next_test();
});
}

View File

@ -21,17 +21,12 @@ function add_ocsp_test(aHost, aExpectedResult, aStaplingEnabled) {
});
}
function run_test() {
do_get_profile();
let fakeOCSPResponder = new HttpServer();
fakeOCSPResponder.registerPrefixHandler("/", function(request, response) {
response.setStatusLine(request.httpVersion, 500, "Internal Server Error");
do_check_true(gExpectOCSPRequest);
function add_tests_in_mode(useInsanity, certDB, otherTestCA) {
add_test(function () {
Services.prefs.setBoolPref("security.use_insanity_verification",
useInsanity);
run_next_test();
});
fakeOCSPResponder.start(8080);
add_tls_server_setup("OCSPStaplingServer");
// In the absence of OCSP stapling, these should actually all work.
add_ocsp_test("ocsp-stapling-good.example.com", Cr.NS_OK, false);
@ -56,25 +51,33 @@ function run_test() {
add_ocsp_test("ocsp-stapling-revoked.example.com",
getXPCOMStatusFromNSS(SEC_ERROR_REVOKED_CERTIFICATE), true);
// SEC_ERROR_OCSP_INVALID_SIGNING_CERT vs SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE
// depends on whether the CA that signed the response is a trusted CA.
// This stapled response is from a CA that is untrusted and did not issue
// the server's certificate.
add_ocsp_test("ocsp-stapling-good-other-ca.example.com",
getXPCOMStatusFromNSS(SEC_ERROR_BAD_DATABASE), true);
// SEC_ERROR_BAD_DATABASE vs SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE depends on
// whether the CA that signed the response is a trusted CA.
add_test(function() {
let certdb = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB);
// Another trusted CA that shouldn't be trusted for OCSP responses, etc.
// for the "good" CA.
addCertFromFile(certdb, "tlsserver/other-test-ca.der", "CTu,u,u");
certDB.setCertTrust(otherTestCA, Ci.nsIX509Cert.CA_CERT,
Ci.nsIX509CertDB.UNTRUSTED);
run_next_test();
});
add_ocsp_test("ocsp-stapling-good-other-ca.example.com",
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_INVALID_SIGNING_CERT), true);
// The stapled response is from a CA that is trusted but did not issue the
// server's certificate.
add_test(function() {
certDB.setCertTrust(otherTestCA, Ci.nsIX509Cert.CA_CERT,
Ci.nsIX509CertDB.TRUSTED_SSL);
run_next_test();
});
add_ocsp_test("ocsp-stapling-good-other-ca.example.com",
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE),
true);
// TODO: Test the case where the signing cert can't be found at all, which
// will result in SEC_ERROR_BAD_DATABASE in the NSS classic case.
add_ocsp_test("ocsp-stapling-malformed.example.com",
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_MALFORMED_REQUEST), true);
add_ocsp_test("ocsp-stapling-srverr.example.com",
@ -106,11 +109,6 @@ function run_test() {
// ocsp-stapling-expired.example.com and
// ocsp-stapling-expired-fresh-ca.example.com are handled in
// test_ocsp_stapling_expired.js
add_test(function() { fakeOCSPResponder.stop(run_next_test); });
add_test(check_ocsp_stapling_telemetry);
run_next_test();
}
function check_ocsp_stapling_telemetry() {
@ -118,10 +116,38 @@ function check_ocsp_stapling_telemetry() {
.getService(Ci.nsITelemetry)
.getHistogramById("SSL_OCSP_STAPLING")
.snapshot();
do_check_eq(histogram.counts[0], 0); // histogram bucket 0 is unused
do_check_eq(histogram.counts[1], 1); // 1 connection with a good response
do_check_eq(histogram.counts[2], 14); // 14 connections with no stapled resp.
do_check_eq(histogram.counts[3], 0); // 0 connections with an expired response
do_check_eq(histogram.counts[4], 11); // 11 connections with bad responses
do_check_eq(histogram.counts[0], 2 * 0); // histogram bucket 0 is unused
do_check_eq(histogram.counts[1], 2 * 1); // 1 connection with a good response
do_check_eq(histogram.counts[2], 2 * 14); // 14 connections with no stapled resp.
do_check_eq(histogram.counts[3], 2 * 0); // 0 connections with an expired response
do_check_eq(histogram.counts[4], 2 * 11); // 11 connections with bad responses
run_next_test();
}
function run_test() {
do_get_profile();
let certDB = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB);
let otherTestCAFile = do_get_file("tlsserver/other-test-ca.der", false);
let otherTestCADER = readFile(otherTestCAFile);
let otherTestCA = certDB.constructX509(otherTestCADER, otherTestCADER.length);
let fakeOCSPResponder = new HttpServer();
fakeOCSPResponder.registerPrefixHandler("/", function (request, response) {
response.setStatusLine(request.httpVersion, 500, "Internal Server Error");
do_check_true(gExpectOCSPRequest);
});
fakeOCSPResponder.start(8080);
add_tls_server_setup("OCSPStaplingServer");
add_tests_in_mode(true, certDB, otherTestCA);
add_tests_in_mode(false, certDB, otherTestCA);
add_test(function () {
fakeOCSPResponder.stop(check_ocsp_stapling_telemetry);
});
run_next_test();
}

View File

@ -25,23 +25,27 @@ function add_ocsp_test(aHost, aExpectedResult, aOCSPResponseToServe) {
});
}
function run_test() {
do_get_profile();
Services.prefs.setBoolPref("security.ssl.enable_ocsp_stapling", true);
let args = [ ["good", "localhostAndExampleCom", "unused" ],
["expiredresponse", "localhostAndExampleCom", "unused"],
["oldvalidperiod", "localhostAndExampleCom", "unused"],
["revoked", "localhostAndExampleCom", "unused"] ];
let ocspResponses = generateOCSPResponses(args, "tlsserver");
// Fresh response, certificate is good.
let ocspResponseGood = ocspResponses[0];
// Expired response, certificate is good.
let expiredOCSPResponseGood = ocspResponses[1];
// Fresh signature, old validity period, certificate is good.
let oldValidityPeriodOCSPResponseGood = ocspResponses[2];
// Fresh signature, certificate is revoked.
let ocspResponseRevoked = ocspResponses[3];
do_get_profile();
Services.prefs.setBoolPref("security.ssl.enable_ocsp_stapling", true);
let args = [["good", "localhostAndExampleCom", "unused"],
["expiredresponse", "localhostAndExampleCom", "unused"],
["oldvalidperiod", "localhostAndExampleCom", "unused"],
["revoked", "localhostAndExampleCom", "unused"],
["unknown", "localhostAndExampleCom", "unused"],
];
let ocspResponses = generateOCSPResponses(args, "tlsserver");
// Fresh response, certificate is good.
let ocspResponseGood = ocspResponses[0];
// Expired response, certificate is good.
let expiredOCSPResponseGood = ocspResponses[1];
// Fresh signature, old validity period, certificate is good.
let oldValidityPeriodOCSPResponseGood = ocspResponses[2];
// Fresh signature, certificate is revoked.
let ocspResponseRevoked = ocspResponses[3];
// Fresh signature, certificate is unknown.
let ocspResponseUnknown = ocspResponses[4];
function run_test() {
let ocspResponder = new HttpServer();
ocspResponder.registerPrefixHandler("/", function(request, response) {
response.setStatusLine(request.httpVersion, 200, "OK");
@ -50,8 +54,21 @@ function run_test() {
gOCSPRequestCount++;
});
ocspResponder.start(8080);
add_tls_server_setup("OCSPStaplingServer");
add_tests_in_mode(true);
add_tests_in_mode(false);
add_test(function () { ocspResponder.stop(run_next_test); });
add_test(check_ocsp_stapling_telemetry);
run_next_test();
}
function add_tests_in_mode(useInsanity)
{
add_test(function () {
Services.prefs.setBoolPref("security.use_insanity_verification",
useInsanity);
run_next_test();
});
// In these tests, the OCSP stapling server gives us a stapled
// response based on the host name ("ocsp-stapling-expired" or
@ -80,9 +97,9 @@ function run_test() {
add_ocsp_test("ocsp-stapling-expired-fresh-ca.example.com",
getXPCOMStatusFromNSS(SEC_ERROR_REVOKED_CERTIFICATE),
ocspResponseRevoked);
add_test(function() { ocspResponder.stop(run_next_test); });
add_test(check_ocsp_stapling_telemetry);
run_next_test();
add_ocsp_test("ocsp-stapling-expired.example.com",
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_UNKNOWN_CERT),
ocspResponseUnknown);
}
function check_ocsp_stapling_telemetry() {
@ -90,10 +107,10 @@ function check_ocsp_stapling_telemetry() {
.getService(Ci.nsITelemetry)
.getHistogramById("SSL_OCSP_STAPLING")
.snapshot();
do_check_eq(histogram.counts[0], 0); // histogram bucket 0 is unused
do_check_eq(histogram.counts[1], 0); // 0 connections with a good response
do_check_eq(histogram.counts[2], 0); // 0 connections with no stapled resp.
do_check_eq(histogram.counts[3], 8); // 8 connections with an expired response
do_check_eq(histogram.counts[4], 0); // 0 connections with bad responses
do_check_eq(histogram.counts[0], 2 * 0); // histogram bucket 0 is unused
do_check_eq(histogram.counts[1], 2 * 0); // 0 connections with a good response
do_check_eq(histogram.counts[2], 2 * 0); // 0 connections with no stapled resp.
do_check_eq(histogram.counts[3], 2 * 9); // 8 connections with an expired response
do_check_eq(histogram.counts[4], 2 * 0); // 0 connections with bad responses
run_next_test();
}

View File

@ -33,11 +33,23 @@ function run_test() {
add_tls_server_setup("OCSPStaplingServer");
add_ocsp_test("ocsp-stapling-with-intermediate.example.com", Cr.NS_OK);
add_test(function() { ocspResponder.stop(run_next_test); });
add_tests_in_mode(true);
add_tests_in_mode(false);
add_test(function () { ocspResponder.stop(run_next_test); });
add_test(function() {
do_check_eq(gOCSPRequestCount, 0);
run_next_test();
});
run_next_test();
}
function add_tests_in_mode(useInsanity) {
add_test(function () {
Services.prefs.setBoolPref("security.use_insanity_verification",
useInsanity);
run_next_test();
});
add_ocsp_test("ocsp-stapling-with-intermediate.example.com", Cr.NS_OK);
}

View File

@ -20,8 +20,6 @@ if CONFIG['OS_ARCH'] == 'WINNT':
'/xpcom/base',
]
DEFINES['MOZILLA_INTERNAL_API'] = True
# component libraries
additional_defines = (
'MOZ_AUTH_EXTENSION',

View File

@ -1,9 +0,0 @@
# 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/.
# Additional debugging info is exposed by setting the MOZ_EM_DEBUG
# environment variable when building.
ifneq (,$(MOZ_EM_DEBUG))
DEFINES += -DMOZ_EM_DEBUG=1
endif

View File

@ -53,7 +53,7 @@ if CONFIG['MOZ_UPDATE_CHANNEL'] not in ('aurora', 'beta', 'release', 'esr'):
DEFINES['MOZ_EXTENSIONS_DB_SCHEMA'] = 16
# Additional debugging info is exposed in debug builds
if CONFIG['MOZ_DEBUG']:
if CONFIG['MOZ_EM_DEBUG']:
DEFINES['MOZ_EM_DEBUG'] = 1
JAR_MANIFESTS += ['jar.mn']
JAR_MANIFESTS += ['jar.mn']

View File

@ -263,28 +263,6 @@ WinUtils::GetMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
return ::GetMessageW(aMsg, aWnd, aFirstMessage, aLastMessage);
}
/* static */
void
WinUtils::WaitForMessage()
{
DWORD result = ::MsgWaitForMultipleObjectsEx(0, nullptr, INFINITE, QS_ALLINPUT,
MWMO_INPUTAVAILABLE);
NS_WARN_IF_FALSE(result != WAIT_FAILED, "Wait failed");
// This idiom is taken from the Chromium ipc code, see
// ipc/chromium/src/base/message+puimp_win.cpp:270.
// The intent is to avoid a busy wait when MsgWaitForMultipleObjectsEx
// returns quickly but PeekMessage would not return a message.
if (result == WAIT_OBJECT_0) {
MSG msg = {0};
DWORD queue_status = ::GetQueueStatus(QS_MOUSE);
if (HIWORD(queue_status) & QS_MOUSE &&
!PeekMessage(&msg, nullptr, WM_MOUSEFIRST, WM_MOUSELAST, PM_NOREMOVE)) {
::WaitMessage();
}
}
}
/* static */
bool
WinUtils::GetRegistryKey(HKEY aRoot,

View File

@ -95,19 +95,6 @@ public:
UINT aLastMessage, UINT aOption);
static bool GetMessage(LPMSG aMsg, HWND aWnd, UINT aFirstMessage,
UINT aLastMessage);
/**
* Wait until a message is ready to be processed.
* Prefer using this method to directly calling ::WaitMessage since
* ::WaitMessage will wait if there is an unread message in the queue.
* That can cause freezes until another message enters the queue if the
* message is marked read by a call to PeekMessage which the caller is
* not aware of (e.g., from a different thread).
* Note that this method may cause sync dispatch of sent (as opposed to
* posted) messages.
*/
static void WaitForMessage();
/**
* Gets the value of a string-typed registry value.
*

View File

@ -282,7 +282,7 @@ nsAppShell::ProcessNextNativeEvent(bool mayWait)
} else if (mayWait) {
// Block and wait for any posted application message
mozilla::HangMonitor::Suspend();
WinUtils::WaitForMessage();
::WaitMessage();
}
} while (!gotMessage && mayWait);

View File

@ -52,8 +52,8 @@ nsThreadPool::nsThreadPool()
: mThreadLimit(DEFAULT_THREAD_LIMIT)
, mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT)
, mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT)
, mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE)
, mIdleCount(0)
, mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE)
, mShutdown(false)
{
}
@ -256,7 +256,7 @@ nsThreadPool::IsOnCurrentThread(bool *result)
{
ReentrantMonitorAutoEnter mon(mEvents.GetReentrantMonitor());
nsIThread* thread = NS_GetCurrentThread();
for (uint32_t i = 0; i < mThreads.Count(); ++i) {
for (uint32_t i = 0; i < static_cast<uint32_t>(mThreads.Count()); ++i) {
if (mThreads[i] == thread) {
*result = true;
return NS_OK;