Bug 901698 - Implement OCSP-must-staple; r=keeler

This commit is contained in:
Mark Goodwin 2015-11-13 16:49:08 +00:00
parent 511f8120df
commit f2e92f7de5
15 changed files with 151 additions and 14 deletions

View File

@ -28,6 +28,7 @@ namespace mozilla { namespace psm {
const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1;
const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2;
const CertVerifier::Flags CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST = 4;
CertVerifier::CertVerifier(OcspDownloadConfig odc,
OcspStrictConfig osc,
@ -583,6 +584,29 @@ CertVerifier::VerifySSLServerCert(CERTCertificate* peerCert,
PR_SetError(MapResultToPRErrorCode(result), 0);
return SECFailure;
}
Input stapledOCSPResponseInput;
Input* responseInputPtr = nullptr;
if (stapledOCSPResponse) {
result = stapledOCSPResponseInput.Init(stapledOCSPResponse->data,
stapledOCSPResponse->len);
if (result != Success) {
// The stapled OCSP response was too big.
PR_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE, 0);
return SECFailure;
}
responseInputPtr = &stapledOCSPResponseInput;
}
if (!(flags & FLAG_TLS_IGNORE_STATUS_REQUEST)) {
result = CheckTLSFeaturesAreSatisfied(peerCertInput, responseInputPtr);
if (result != Success) {
PR_SetError(MapResultToPRErrorCode(result), 0);
return SECFailure;
}
}
Input hostnameInput;
result = hostnameInput.Init(uint8_t_ptr_cast(hostname), strlen(hostname));
if (result != Success) {

View File

@ -54,6 +54,8 @@ public:
static const Flags FLAG_LOCAL_ONLY;
// Don't perform fallback DV validation on EV validation failure.
static const Flags FLAG_MUST_BE_EV;
// TLS feature request_status should be ignored
static const Flags FLAG_TLS_IGNORE_STATUS_REQUEST;
// These values correspond to the SSL_OCSP_STAPLING telemetry.
enum OCSPStaplingStatus {

View File

@ -319,3 +319,4 @@ MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE=A certificate that is not ye
MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH=The signature algorithm in the signature field of the certificate does not match the algorithm in its signatureAlgorithm field.
MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING=The OCSP response does not include a status for the certificate being verified.
MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG=The server presented a certificate that is valid for too long.
MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING=A required TLS feature is missing.

View File

@ -214,7 +214,7 @@ void StopSSLServerCertVerificationThreads()
namespace {
void
LogInvalidCertError(TransportSecurityInfo* socketInfo,
LogInvalidCertError(nsNSSSocketInfo* socketInfo,
PRErrorCode errorCode,
::mozilla::psm::SSLErrorMessageType errorMessageType)
{
@ -236,7 +236,7 @@ class SSLServerCertVerificationResult : public nsRunnable
public:
NS_DECL_NSIRUNNABLE
SSLServerCertVerificationResult(TransportSecurityInfo* infoObject,
SSLServerCertVerificationResult(nsNSSSocketInfo* infoObject,
PRErrorCode errorCode,
Telemetry::ID telemetryID = Telemetry::HistogramCount,
uint32_t telemetryValue = -1,
@ -245,7 +245,7 @@ public:
void Dispatch();
private:
const RefPtr<TransportSecurityInfo> mInfoObject;
const RefPtr<nsNSSSocketInfo> mInfoObject;
public:
const PRErrorCode mErrorCode;
const SSLErrorMessageType mErrorMessageType;
@ -258,7 +258,7 @@ class CertErrorRunnable : public SyncRunnableBase
public:
CertErrorRunnable(const void* fdForLogging,
nsIX509Cert* cert,
TransportSecurityInfo* infoObject,
nsNSSSocketInfo* infoObject,
PRErrorCode defaultErrorCodeToReport,
uint32_t collectedErrors,
PRErrorCode errorCodeTrust,
@ -282,7 +282,7 @@ private:
const void* const mFdForLogging; // may become an invalid pointer; do not dereference
const nsCOMPtr<nsIX509Cert> mCert;
const RefPtr<TransportSecurityInfo> mInfoObject;
const RefPtr<nsNSSSocketInfo> mInfoObject;
const PRErrorCode mDefaultErrorCodeToReport;
const uint32_t mCollectedErrors;
const PRErrorCode mErrorCodeTrust;
@ -622,7 +622,7 @@ CertErrorRunnable::RunOnTargetThread()
CertErrorRunnable*
CreateCertErrorRunnable(CertVerifier& certVerifier,
PRErrorCode defaultErrorCodeToReport,
TransportSecurityInfo* infoObject,
nsNSSSocketInfo* infoObject,
CERTCertificate* cert,
const void* fdForLogging,
uint32_t providerFlags,
@ -715,7 +715,7 @@ public:
// Must be called only on the socket transport thread
static SECStatus Dispatch(const RefPtr<SharedCertVerifier>& certVerifier,
const void* fdForLogging,
TransportSecurityInfo* infoObject,
nsNSSSocketInfo* infoObject,
CERTCertificate* serverCert,
ScopedCERTCertList& peerCertChain,
SECItem* stapledOCSPResponse,
@ -728,7 +728,7 @@ private:
// Must be called only on the socket transport thread
SSLServerCertVerificationJob(const RefPtr<SharedCertVerifier>& certVerifier,
const void* fdForLogging,
TransportSecurityInfo* infoObject,
nsNSSSocketInfo* infoObject,
CERTCertificate* cert,
CERTCertList* peerCertChain,
SECItem* stapledOCSPResponse,
@ -737,7 +737,7 @@ private:
PRTime prtime);
const RefPtr<SharedCertVerifier> mCertVerifier;
const void* const mFdForLogging;
const RefPtr<TransportSecurityInfo> mInfoObject;
const RefPtr<nsNSSSocketInfo> mInfoObject;
const ScopedCERTCertificate mCert;
ScopedCERTCertList mPeerCertChain;
const uint32_t mProviderFlags;
@ -749,7 +749,7 @@ private:
SSLServerCertVerificationJob::SSLServerCertVerificationJob(
const RefPtr<SharedCertVerifier>& certVerifier, const void* fdForLogging,
TransportSecurityInfo* infoObject, CERTCertificate* cert,
nsNSSSocketInfo* infoObject, CERTCertificate* cert,
CERTCertList* peerCertChain, SECItem* stapledOCSPResponse,
uint32_t providerFlags, Time time, PRTime prtime)
: mCertVerifier(certVerifier)
@ -1200,7 +1200,7 @@ GatherSuccessfulValidationTelemetry(const ScopedCERTCertList& certList)
SECStatus
AuthCertificate(CertVerifier& certVerifier,
TransportSecurityInfo* infoObject,
nsNSSSocketInfo* infoObject,
CERTCertificate* cert,
ScopedCERTCertList& peerCertChain,
SECItem* stapledOCSPResponse,
@ -1225,10 +1225,16 @@ AuthCertificate(CertVerifier& certVerifier,
SignatureDigestStatus sigDigestStatus = SignatureDigestStatus::NeverChecked;
PinningTelemetryInfo pinningTelemetryInfo;
int flags = 0;
if (!infoObject->SharedState().IsOCSPStaplingEnabled() ||
!infoObject->SharedState().IsOCSPMustStapleEnabled()) {
flags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
}
rv = certVerifier.VerifySSLServerCert(cert, stapledOCSPResponse,
time, infoObject,
infoObject->GetHostNameRaw(),
saveIntermediates, 0, &certList,
saveIntermediates, flags, &certList,
&evOidPolicy, &ocspStaplingStatus,
&keySizeStatus, &sigDigestStatus,
&pinningTelemetryInfo);
@ -1327,7 +1333,7 @@ AuthCertificate(CertVerifier& certVerifier,
SSLServerCertVerificationJob::Dispatch(
const RefPtr<SharedCertVerifier>& certVerifier,
const void* fdForLogging,
TransportSecurityInfo* infoObject,
nsNSSSocketInfo* infoObject,
CERTCertificate* serverCert,
ScopedCERTCertList& peerCertChain,
SECItem* stapledOCSPResponse,
@ -1662,7 +1668,7 @@ void EnsureServerVerificationInitialized()
}
SSLServerCertVerificationResult::SSLServerCertVerificationResult(
TransportSecurityInfo* infoObject, PRErrorCode errorCode,
nsNSSSocketInfo* infoObject, PRErrorCode errorCode,
Telemetry::ID telemetryID, uint32_t telemetryValue,
SSLErrorMessageType errorMessageType)
: mInfoObject(infoObject)

View File

@ -39,12 +39,17 @@ public:
{
mOCSPStaplingEnabled = staplingEnabled;
}
void SetOCSPMustStapleEnabled(bool mustStapleEnabled)
{
mOCSPMustStapleEnabled = mustStapleEnabled;
}
// The following methods may be called from any thread
bool SocketCreated();
void NoteSocketCreated();
static void NoteCertOverrideServiceInstantiated();
bool IsOCSPStaplingEnabled() const { return mOCSPStaplingEnabled; }
bool IsOCSPMustStapleEnabled() const { return mOCSPMustStapleEnabled; }
private:
~SharedSSLState();
@ -61,6 +66,7 @@ private:
Mutex mMutex;
bool mSocketCreated;
bool mOCSPStaplingEnabled;
bool mOCSPMustStapleEnabled;
};
SharedSSLState* PublicSSLState();

View File

@ -847,6 +847,11 @@ void nsNSSComponent::setValidationOptions(bool isInitialSetting,
PublicSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled);
PrivateSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled);
bool ocspMustStapleEnabled = Preferences::GetBool("security.ssl.enable_ocsp_must_staple",
false);
PublicSSLState()->SetOCSPMustStapleEnabled(ocspMustStapleEnabled);
PrivateSSLState()->SetOCSPMustStapleEnabled(ocspMustStapleEnabled);
CertVerifier::PinningMode pinningMode =
static_cast<CertVerifier::PinningMode>
(Preferences::GetInt("security.cert_pinning.enforcement_level",
@ -1332,6 +1337,7 @@ nsNSSComponent::Observe(nsISupports* aSubject, const char* aTopic,
prefName.EqualsLiteral("security.OCSP.GET.enabled") ||
prefName.EqualsLiteral("security.pki.cert_short_lifetime_in_days") ||
prefName.EqualsLiteral("security.ssl.enable_ocsp_stapling") ||
prefName.EqualsLiteral("security.ssl.enable_ocsp_must_staple") ||
prefName.EqualsLiteral("security.cert_pinning.enforcement_level") ||
prefName.EqualsLiteral("security.pki.sha1_enforcement_level")) {
MutexAutoLock lock(mutex);

View File

@ -185,6 +185,8 @@ static const unsigned int FATAL_ERROR_FLAG = 0x800;
MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING) \
MOZILLA_PKIX_MAP(ERROR_VALIDITY_TOO_LONG, 50, \
MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG) \
MOZILLA_PKIX_MAP(ERROR_REQUIRED_TLS_FEATURE_MISSING, 51, \
MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING) \
MOZILLA_PKIX_MAP(FATAL_ERROR_INVALID_ARGS, FATAL_ERROR_FLAG | 1, \
SEC_ERROR_INVALID_ARGS) \
MOZILLA_PKIX_MAP(FATAL_ERROR_INVALID_STATE, FATAL_ERROR_FLAG | 2, \

View File

@ -147,6 +147,14 @@ Result VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
/* optional out */ Time* thisUpdate = nullptr,
/* optional out */ Time* validThrough = nullptr);
// Check that the TLSFeature extensions in a given end-entity cert (which is
// assumed to have been already validated with BuildCertChain) are satisfied.
// The only feature which we cancurrently process a requirement for is
// status_request (OCSP stapling) so we reject any extension that specifies a
// requirement for another value. Empty extensions are also rejected.
Result CheckTLSFeaturesAreSatisfied(Input& cert,
const Input* stapledOCSPResponse);
} } // namespace mozilla::pkix
#endif // mozilla_pkix_pkix_h

View File

@ -84,6 +84,7 @@ enum ErrorCode
MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH = ERROR_BASE + 7,
MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING = ERROR_BASE + 8,
MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG = ERROR_BASE + 9,
MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING = ERROR_BASE + 10,
};
void RegisterErrorTable();

View File

@ -188,6 +188,11 @@ PathBuildingStep::Check(Input potentialIssuerDER,
}
}
rv = CheckTLSFeatures(subject, potentialIssuer);
if (rv != Success) {
return RecordResult(rv, keepGoing);
}
// RFC 5280, Section 4.2.1.3: "If the keyUsage extension is present, then the
// subject public key MUST NOT be used to verify signatures on certificates
// or CRLs unless the corresponding keyCertSign or cRLSign bit is set."

View File

@ -219,6 +219,10 @@ BackCert::RememberExtension(Reader& extnID, Input extnValue,
static const uint8_t Netscape_certificate_type[] = {
0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01
};
// python DottedOIDToCode.py id-pe-tlsfeature 1.3.6.1.5.5.7.1.24
static const uint8_t id_pe_tlsfeature[] = {
0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x18
};
Input* out = nullptr;
@ -263,6 +267,8 @@ BackCert::RememberExtension(Reader& extnID, Input extnValue,
out = &inhibitAnyPolicy;
} else if (extnID.MatchRest(id_pe_authorityInfoAccess)) {
out = &authorityInfoAccess;
} else if (extnID.MatchRest(id_pe_tlsfeature)) {
out = &requiredTLSFeatures;
} else if (extnID.MatchRest(id_pkix_ocsp_nocheck) && critical) {
// We need to make sure we don't reject delegated OCSP response signing
// certificates that contain the id-pkix-ocsp-nocheck extension marked as

View File

@ -830,6 +830,65 @@ CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA,
return Success;
}
Result
CheckTLSFeatures(const BackCert& subject, BackCert& potentialIssuer)
{
const Input* issuerTLSFeatures = potentialIssuer.GetRequiredTLSFeatures();
if (!issuerTLSFeatures) {
return Success;
}
const Input* subjectTLSFeatures = subject.GetRequiredTLSFeatures();
if (issuerTLSFeatures->GetLength() == 0 ||
!subjectTLSFeatures ||
!InputsAreEqual(*issuerTLSFeatures, *subjectTLSFeatures)) {
return Result::ERROR_REQUIRED_TLS_FEATURE_MISSING;
}
return Success;
}
Result
TLSFeaturesSatisfiedInternal(const Input* requiredTLSFeatures,
const Input* stapledOCSPResponse)
{
if (!requiredTLSFeatures) {
return Success;
}
// RFC 6066 10.2: ExtensionType status_request
const static uint8_t status_request = 5;
const static uint8_t status_request_bytes[] = { status_request };
Reader input(*requiredTLSFeatures);
return der::NestedOf(input, der::SEQUENCE, der::INTEGER,
der::EmptyAllowed::No, [&](Reader& r) {
if (!r.MatchRest(status_request_bytes)) {
return Result::ERROR_REQUIRED_TLS_FEATURE_MISSING;
}
if (!stapledOCSPResponse) {
return Result::ERROR_REQUIRED_TLS_FEATURE_MISSING;
}
return Result::Success;
});
}
Result
CheckTLSFeaturesAreSatisfied(Input& cert,
const Input* stapledOCSPResponse)
{
BackCert backCert(cert, EndEntityOrCA::MustBeEndEntity, nullptr);
Result rv = backCert.Init();
if (rv != Success) {
return rv;
}
return TLSFeaturesSatisfiedInternal(backCert.GetRequiredTLSFeatures(),
stapledOCSPResponse);
}
Result
CheckIssuerIndependentProperties(TrustDomain& trustDomain,
const BackCert& cert,

View File

@ -55,6 +55,10 @@ Result ParseValidity(Input encodedValidity,
/*optional out*/ Time* notAfterOut = nullptr);
Result CheckValidity(Time time, Time notBefore, Time notAfter);
// Check that a subject has TLS Feature (rfc7633) requirements that match its
// potential issuer
Result CheckTLSFeatures(const BackCert& subject, BackCert& potentialIssuer);
} } // namespace mozilla::pkix
#endif // mozilla_pkix_pkixcheck_h

View File

@ -202,6 +202,8 @@ RegisterErrorTable()
"verified." },
{ "MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG",
"The server presented a certificate that is valid for too long." },
{ "MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING",
"A required TLS feature is missing." },
};
// Note that these error strings are not localizable.
// When these strings change, update the localization information too.

View File

@ -102,6 +102,10 @@ public:
{
return MaybeInput(subjectAltName);
}
const Input* GetRequiredTLSFeatures() const
{
return MaybeInput(requiredTLSFeatures);
}
private:
const Input der;
@ -144,6 +148,7 @@ private:
Input nameConstraints;
Input subjectAltName;
Input criticalNetscapeCertificateType;
Input requiredTLSFeatures;
Result RememberExtension(Reader& extnID, Input extnValue, bool critical,
/*out*/ bool& understood);