Skip to content

Commit

Permalink
Bug 1039064: Use strongly-typed enum instead of NSPR-style error hand…
Browse files Browse the repository at this point in the history
…ling, r=keeler

--HG--
extra : rebase_source : 4f3e41916cd7e2c74679d468eeeb702af3321532
  • Loading branch information
briansmith committed Jul 18, 2014
1 parent 7327d66 commit 28685ac
Show file tree
Hide file tree
Showing 28 changed files with 1,210 additions and 1,211 deletions.
11 changes: 6 additions & 5 deletions lib/mozpkix/include/pkix/Input.h
Expand Up @@ -27,6 +27,7 @@

#include "pkix/nullptr.h"
#include "pkix/Result.h"
#include "seccomon.h"
#include "stdint.h"

namespace mozilla { namespace pkix {
Expand Down Expand Up @@ -55,11 +56,11 @@ class Input
{
if (input) {
// already initialized
return Fail(SEC_ERROR_INVALID_ARGS);
return Result::FATAL_ERROR_INVALID_ARGS;
}
if (!data || len > 0xffffu) {
// input too large
return Fail(SEC_ERROR_BAD_DER);
return Result::ERROR_BAD_DER;
}

// XXX: this->input = input bug was not caught by tests! Why not?
Expand All @@ -77,7 +78,7 @@ class Input
return rv;
}
if (memcmp(input, expected, expectedLen)) {
return Fail(SEC_ERROR_BAD_DER);
return Result::ERROR_BAD_DER;
}
input += expectedLen;
return Success;
Expand Down Expand Up @@ -196,7 +197,7 @@ class Input
Result EnsureLength(uint16_t len)
{
if (static_cast<size_t>(end - input) < len) {
return Fail(SEC_ERROR_BAD_DER);
return Result::ERROR_BAD_DER;
}
return Success;
}
Expand All @@ -219,7 +220,7 @@ class Input
{
if (&mark.input != this || mark.mark > input) {
PR_NOT_REACHED("invalid mark");
return Fail(SEC_ERROR_INVALID_ARGS);
return Result::FATAL_ERROR_INVALID_ARGS;
}
item.type = type;
item.data = const_cast<uint8_t*>(mark.mark);
Expand Down
115 changes: 67 additions & 48 deletions lib/mozpkix/include/pkix/Result.h
Expand Up @@ -25,63 +25,82 @@
#ifndef mozilla_pkix__Result_h
#define mozilla_pkix__Result_h

#include "prerror.h"
#include "seccomon.h"
#include "secerr.h"
#include "pkix/enumclass.h"

namespace mozilla { namespace pkix {

enum Result
static const unsigned int FATAL_ERROR_FLAG = 0x800;

MOZILLA_PKIX_ENUM_CLASS Result
{
Success = 0,
FatalError = -1, // An error was encountered that caused path building
// to stop immediately. example: out-of-memory.
RecoverableError = -2 // an error that will cause path building to continue
// searching for alternative paths. example: expired
// certificate.
};

// When returning errors, use this function instead of calling PR_SetError
// directly. This helps ensure that we always call PR_SetError when we return
// an error code. This is a useful place to set a breakpoint when a debugging
// a certificate verification failure.
inline Result
Fail(Result result, PRErrorCode errorCode)
{
PR_ASSERT(result != Success);
PR_SetError(errorCode, 0);
return result;
}
ERROR_BAD_DER = 1,
ERROR_CA_CERT_INVALID = 2,
ERROR_BAD_SIGNATURE = 3,
ERROR_CERT_BAD_ACCESS_LOCATION = 4,
ERROR_CERT_NOT_IN_NAME_SPACE = 5,
ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = 6,
ERROR_CONNECT_REFUSED = 7,
ERROR_EXPIRED_CERTIFICATE = 8,
ERROR_EXTENSION_VALUE_INVALID = 9,
ERROR_INADEQUATE_CERT_TYPE = 10,
ERROR_INADEQUATE_KEY_USAGE = 11,
ERROR_INVALID_ALGORITHM = 12,
ERROR_INVALID_TIME = 13,
ERROR_KEY_PINNING_FAILURE = 14,
ERROR_PATH_LEN_CONSTRAINT_INVALID = 15,
ERROR_POLICY_VALIDATION_FAILED = 16,
ERROR_REVOKED_CERTIFICATE = 17,
ERROR_UNKNOWN_CRITICAL_EXTENSION = 18,
ERROR_UNKNOWN_ISSUER = 19,
ERROR_UNTRUSTED_CERT = 20,
ERROR_UNTRUSTED_ISSUER = 21,

inline Result
MapSECStatus(SECStatus srv)
{
if (srv == SECSuccess) {
return Success;
}

PRErrorCode error = PORT_GetError();
switch (error) {
case SEC_ERROR_EXTENSION_NOT_FOUND:
return RecoverableError;

case PR_INVALID_STATE_ERROR:
case SEC_ERROR_INVALID_ARGS:
case SEC_ERROR_LIBRARY_FAILURE:
case SEC_ERROR_NO_MEMORY:
return FatalError;
}

// TODO: PORT_Assert(false); // we haven't classified the error yet
return RecoverableError;
}
ERROR_OCSP_BAD_SIGNATURE = 22,
ERROR_OCSP_INVALID_SIGNING_CERT = 23,
ERROR_OCSP_MALFORMED_REQUEST = 24,
ERROR_OCSP_MALFORMED_RESPONSE = 25,
ERROR_OCSP_OLD_RESPONSE = 26,
ERROR_OCSP_REQUEST_NEEDS_SIG = 27,
ERROR_OCSP_RESPONDER_CERT_INVALID = 28,
ERROR_OCSP_SERVER_ERROR = 29,
ERROR_OCSP_TRY_SERVER_LATER = 30,
ERROR_OCSP_UNAUTHORIZED_REQUEST = 31,
ERROR_OCSP_UNKNOWN_RESPONSE_STATUS = 32,
ERROR_OCSP_UNKNOWN_CERT = 33,
ERROR_OCSP_FUTURE_RESPONSE = 34,

ERROR_UNKNOWN_ERROR = 35,

ERROR_INVALID_KEY = 36,
ERROR_UNSUPPORTED_KEYALG = 37,

// Keep this in sync with MAP_LIST in pkixnss.cpp

FATAL_ERROR_INVALID_ARGS = FATAL_ERROR_FLAG | 1,
FATAL_ERROR_INVALID_STATE = FATAL_ERROR_FLAG | 2,
FATAL_ERROR_LIBRARY_FAILURE = FATAL_ERROR_FLAG | 3,
FATAL_ERROR_NO_MEMORY = FATAL_ERROR_FLAG | 4,

// Keep this in sync with MAP_LIST in pkixnss.cpp
};

// We write many comparisons as (x != Success), and this shortened name makes
// those comparisons clearer, especially because the shortened name often
// results in less line wrapping.
//
// Visual Studio before VS2013 does not support "enum class," so
// Result::Success will already be visible in this scope, and compilation will
// fail if we try to define a variable with that name here.
#if !defined(_MSC_VER) || (_MSC_VER >= 1700)
static const Result Success = Result::Success;
#endif

inline Result
Fail(PRErrorCode errorCode)
inline bool
IsFatalError(Result rv)
{
PR_ASSERT(errorCode != 0);
PR_SetError(errorCode, 0);
return mozilla::pkix::MapSECStatus(SECFailure);
return static_cast<unsigned int>(rv) & FATAL_ERROR_FLAG;
}

} } // namespace mozilla::pkix
Expand Down
87 changes: 33 additions & 54 deletions lib/mozpkix/include/pkix/pkix.h
Expand Up @@ -25,9 +25,7 @@
#ifndef mozilla_pkix__pkix_h
#define mozilla_pkix__pkix_h

#include "pkix/nullptr.h"
#include "pkixtypes.h"
#include "prtime.h"

namespace mozilla { namespace pkix {

Expand Down Expand Up @@ -60,15 +58,15 @@ namespace mozilla { namespace pkix {
//
// The ranking is:
//
// 1. Active distrust (SEC_ERROR_UNTRUSTED_CERT).
// 1. Active distrust (Result::ERROR_UNTRUSTED_CERT).
// 2. Problems with issuer-independent properties for CA certificates.
// 3. Unknown issuer (SEC_ERROR_UNKNOWN_ISSUER).
// 3. Unknown issuer (Result::ERROR_UNKNOWN_ISSUER).
// 4. Problems with issuer-independent properties for EE certificates.
// 5. 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.
// In particular, if BuildCertChain returns Result::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
Expand All @@ -77,70 +75,51 @@ namespace mozilla { namespace pkix {
// 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.
// Result::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.
// Result::ERROR_UNTRUSTED_CERT means that the end-entity certificate was
// actively distrusted.
// Result::SEC_ERROR_UNTRUSTED_ISSUER means that path building failed because
// of active distrust.
// TODO(bug 968451): Document more of these.

SECStatus BuildCertChain(TrustDomain& trustDomain, const SECItem& cert,
PRTime time, EndEntityOrCA endEntityOrCA,
KeyUsage requiredKeyUsageIfPresent,
KeyPurposeId requiredEKUIfPresent,
const CertPolicyId& requiredPolicy,
/*optional*/ const SECItem* stapledOCSPResponse);
Result BuildCertChain(TrustDomain& trustDomain, const SECItem& cert,
PRTime time, EndEntityOrCA endEntityOrCA,
KeyUsage requiredKeyUsageIfPresent,
KeyPurposeId requiredEKUIfPresent,
const CertPolicyId& requiredPolicy,
/*optional*/ const SECItem* stapledOCSPResponse);

// Verify the given signed data using the given public key.
SECStatus VerifySignedData(const SignedDataWithSignature& sd,
const SECItem& subjectPublicKeyInfo,
void* pkcs11PinArg);

// The return value, if non-null, is owned by the arena and MUST NOT be freed.
SECItem* CreateEncodedOCSPRequest(TrustDomain& trustDomain, PLArenaPool* arena,
const CertID& certID);
static const size_t OCSP_REQUEST_MAX_LENGTH = 127;
Result CreateEncodedOCSPRequest(TrustDomain& trustDomain,
const struct CertID& certID,
/*out*/ uint8_t (&out)[OCSP_REQUEST_MAX_LENGTH],
/*out*/ size_t& outLen);

// The out parameter expired will be true if the response has expired. If the
// response also indicates a revoked or unknown certificate, that error
// will be returned by PR_GetError(). Otherwise, SEC_ERROR_OCSP_OLD_RESPONSE
// will be returned by PR_GetError() for an expired response.
// will be returned. Otherwise, REsult::ERROR_OCSP_OLD_RESPONSE will be
// returned for an expired response.
//
// The optional parameter thisUpdate will be the thisUpdate value of
// the encoded response if it is considered trustworthy. Only
// good, unknown, or revoked responses that verify correctly are considered
// trustworthy. If the response is not trustworthy, thisUpdate will be 0.
// Similarly, the optional parameter validThrough will be the time through
// which the encoded response is considered trustworthy (that is, if a response had a
// thisUpdate time of validThrough, it would be considered trustworthy).
SECStatus VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
const CertID& certID, PRTime time,
uint16_t maxLifetimeInDays,
const SECItem& encodedResponse,
/* out */ bool& expired,
/* optional out */ PRTime* thisUpdate = nullptr,
/* optional out */ PRTime* validThrough = nullptr);

// Computes the SHA-1 hash of the data in the current item.
//
// item contains the data to hash.
// digestBuf must point to a buffer to where the SHA-1 hash will be written.
// digestBufLen must be 20 (the length of a SHA-1 hash,
// TrustDomain::DIGEST_LENGTH).
//
// TODO(bug 966856): Add SHA-2 support
// TODO: Taking the output buffer as (uint8_t*, size_t) is counter to our
// other, extensive, memory safety efforts in mozilla::pkix, and we should find
// a way to provide a more-obviously-safe interface.
SECStatus DigestBuf(const SECItem& item, /*out*/ uint8_t* digestBuf,
size_t digestBufLen);

// Checks, for RSA keys and DSA keys, that the modulus is at least 1024 bits.
SECStatus CheckPublicKey(const SECItem& subjectPublicKeyInfo);
Result VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
const CertID& certID, PRTime time,
uint16_t maxLifetimeInDays,
const SECItem& encodedResponse,
/* out */ bool& expired,
/* optional out */ PRTime* thisUpdate = nullptr,
/* optional out */ PRTime* validThrough = nullptr);

} } // namespace mozilla::pkix

Expand Down
81 changes: 81 additions & 0 deletions lib/mozpkix/include/pkix/pkixnss.h
@@ -0,0 +1,81 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This code is made available to you under your choice of the following sets
* of licensing terms:
*/
/* 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/.
*/
/* Copyright 2013 Mozilla Contributors
*
* 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.
*/

#ifndef mozilla_pkix__pkixnss_h
#define mozilla_pkix__pkixnss_h

#include "pkixtypes.h"
#include "prerror.h"
#include "seccomon.h"

namespace mozilla { namespace pkix {

// Verify the given signed data using the given public key.
Result VerifySignedData(const SignedDataWithSignature& sd,
const SECItem& subjectPublicKeyInfo,
void* pkcs11PinArg);

// Computes the SHA-1 hash of the data in the current item.
//
// item contains the data to hash.
// digestBuf must point to a buffer to where the SHA-1 hash will be written.
// digestBufLen must be 20 (the length of a SHA-1 hash,
// TrustDomain::DIGEST_LENGTH).
//
// TODO(bug 966856): Add SHA-2 support
// TODO: Taking the output buffer as (uint8_t*, size_t) is counter to our
// other, extensive, memory safety efforts in mozilla::pkix, and we should find
// a way to provide a more-obviously-safe interface.
Result DigestBuf(const SECItem& item, /*out*/ uint8_t* digestBuf,
size_t digestBufLen);

// Checks, for RSA keys and DSA keys, that the modulus is at least 1024 bits.
Result CheckPublicKey(const SECItem& subjectPublicKeyInfo);

Result MapPRErrorCodeToResult(PRErrorCode errorCode);
PRErrorCode MapResultToPRErrorCode(Result result);

// Returns the stringified name of the given result, e.g. "Result::Success",
// or nullptr if result is unknown (invalid).
const char* MapResultToName(Result result);

// The error codes within each module must fit in 16 bits. We want these
// errors to fit in the same module as the NSS errors but not overlap with
// any of them. Converting an NSS SEC, NSS SSL, or PSM error to an NS error
// involves negating the value of the error and then synthesizing an error
// in the NS_ERROR_MODULE_SECURITY module. Hence, PSM errors will start at
// a negative value that both doesn't overlap with the current value
// ranges for NSS errors and that will fit in 16 bits when negated.
static const PRErrorCode ERROR_BASE = -0x4000;
static const PRErrorCode ERROR_LIMIT = ERROR_BASE + 1000;

enum ErrorCode {
MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE = ERROR_BASE + 0
};

void RegisterErrorTable();

} } // namespace mozilla::pkix

#endif // mozilla_pkix__pkixnss_h

0 comments on commit 28685ac

Please sign in to comment.