Bug 974715 - Create more flexible OCSP response generation code. r=briansmith, r=cviecco

This commit is contained in:
David Keeler 2014-03-10 14:04:31 -07:00
Родитель 2bd5689ee7
Коммит 954d7d0bfb
11 изменённых файлов: 791 добавлений и 158 удалений

Просмотреть файл

@ -23,6 +23,12 @@ LOCAL_INCLUDES += [
'../insanity/include',
]
DIRS += [
'../insanity/test/lib',
]
FAIL_ON_WARNINGS = True
LIBRARY_NAME = 'certverifier'
FINAL_LIBRARY = 'xul'

Просмотреть файл

@ -0,0 +1,27 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# 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.
UNIFIED_SOURCES += [
'pkixtestutil.cpp',
]
LIBRARY_NAME = 'pkixtestutil'
LOCAL_INCLUDES += [
'../../include',
'../../lib',
]
FAIL_ON_WARNINGS = True

Просмотреть файл

@ -0,0 +1,610 @@
/* -*- 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 "pkixcheck.h"
#include "pkixder.h"
#include "pkixtestutil.h"
#include "cryptohi.h"
#include "hasht.h"
#include "pk11pub.h"
#include "prinit.h"
#include "secder.h"
namespace insanity { namespace test {
class Output
{
public:
Output()
: numItems(0)
, length(0)
{
}
// Makes a shallow copy of the input item. All input items must have a
// lifetime that extends at least to where Squash is called.
der::Result Add(const SECItem* item)
{
PR_ASSERT(item);
PR_ASSERT(item->data);
if (numItems >= MaxSequenceItems) {
return der::Fail(SEC_ERROR_INVALID_ARGS);
}
if (length + item->len > 65535) {
return der::Fail(SEC_ERROR_INVALID_ARGS);
}
contents[numItems] = item;
numItems++;
length += item->len;
return der::Success;
}
SECItem* Squash(PLArenaPool* arena, uint8_t tag)
{
PR_ASSERT(arena);
size_t lengthLength = length < 128 ? 1
: length < 256 ? 2
: 3;
size_t totalLength = 1 + lengthLength + length;
SECItem* output = SECITEM_AllocItem(arena, nullptr, totalLength);
if (!output) {
return nullptr;
}
uint8_t* d = output->data;
*d++ = tag;
EncodeLength(d, length, lengthLength);
d += lengthLength;
for (size_t i = 0; i < numItems; i++) {
memcpy(d, contents[i]->data, contents[i]->len);
d += contents[i]->len;
}
return output;
}
private:
void
EncodeLength(uint8_t* data, size_t length, size_t lengthLength)
{
switch (lengthLength) {
case 1:
data[0] = length;
break;
case 2:
data[0] = 0x81;
data[1] = length;
break;
case 3:
data[0] = 0x82;
data[1] = length / 256;
data[2] = length % 256;
break;
default:
PR_NOT_REACHED("EncodeLength: bad lengthLength");
PR_Abort();
}
}
static const size_t MaxSequenceItems = 5;
const SECItem* contents[MaxSequenceItems];
size_t numItems;
size_t length;
Output(const Output&) /* = delete */;
void operator=(const Output&) /* = delete */;
};
static SECItem* ResponseBytes(OCSPResponseContext& context);
static SECItem* BasicOCSPResponse(OCSPResponseContext& context);
static SECItem* ResponseData(OCSPResponseContext& context);
static SECItem* ResponderID(OCSPResponseContext& context);
static SECItem* KeyHash(OCSPResponseContext& context);
static SECItem* SingleResponse(OCSPResponseContext& context);
static SECItem* CertID(OCSPResponseContext& context);
static SECItem* CertStatus(OCSPResponseContext& context);
static SECItem*
EncodeNested(PLArenaPool* arena, uint8_t tag, SECItem* inner)
{
Output output;
if (output.Add(inner) != der::Success) {
return nullptr;
}
return output.Squash(arena, tag);
}
// A return value of 0 is an error, but this should never happen in practice
// because this function aborts in that case.
static size_t
HashAlgorithmToLength(SECOidTag hashAlg)
{
switch (hashAlg) {
case SEC_OID_SHA1:
return SHA1_LENGTH;
case SEC_OID_SHA256:
return SHA256_LENGTH;
case SEC_OID_SHA384:
return SHA384_LENGTH;
case SEC_OID_SHA512:
return SHA512_LENGTH;
default:
PR_NOT_REACHED("HashAlgorithmToLength: bad hashAlg");
PR_Abort();
}
return 0;
}
static SECItem*
HashedOctetString(PLArenaPool* arena, const SECItem* bytes, SECOidTag hashAlg)
{
size_t hashLen = HashAlgorithmToLength(hashAlg);
if (hashLen == 0) {
return nullptr;
}
SECItem* hashBuf = SECITEM_AllocItem(arena, nullptr, hashLen);
if (!hashBuf) {
return nullptr;
}
if (PK11_HashBuf(hashAlg, hashBuf->data, bytes->data, bytes->len)
!= SECSuccess) {
return nullptr;
}
return EncodeNested(arena, der::OCTET_STRING, hashBuf);
}
static SECItem*
KeyHashHelper(PLArenaPool* arena, const CERTCertificate* cert)
{
// We only need a shallow copy here.
SECItem spk = cert->subjectPublicKeyInfo.subjectPublicKey;
DER_ConvertBitString(&spk); // bits to bytes
return HashedOctetString(arena, &spk, SEC_OID_SHA1);
}
static SECItem*
AlgorithmIdentifier(PLArenaPool* arena, SECOidTag algTag)
{
SECAlgorithmIDStr aid;
aid.algorithm.data = nullptr;
aid.algorithm.len = 0;
aid.parameters.data = nullptr;
aid.parameters.len = 0;
if (SECOID_SetAlgorithmID(arena, &aid, algTag, nullptr) != SECSuccess) {
return nullptr;
}
static const SEC_ASN1Template algorithmIDTemplate[] = {
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SECAlgorithmID) },
{ SEC_ASN1_OBJECT_ID, offsetof(SECAlgorithmID, algorithm) },
{ SEC_ASN1_OPTIONAL | SEC_ASN1_ANY, offsetof(SECAlgorithmID, parameters) },
{ 0 }
};
SECItem* algorithmID = SEC_ASN1EncodeItem(arena, nullptr, &aid,
algorithmIDTemplate);
return algorithmID;
}
static SECItem*
PRTimeToEncodedTime(PLArenaPool* arena, PRTime time)
{
SECItem derTime;
if (DER_TimeToGeneralizedTimeArena(arena, &derTime, time) != SECSuccess) {
return nullptr;
}
return EncodeNested(arena, der::GENERALIZED_TIME, &derTime);
}
SECItem*
CreateEncodedOCSPResponse(OCSPResponseContext& context)
{
if (!context.arena || !context.cert || !context.issuerCert ||
!context.signerCert) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return nullptr;
}
// OCSPResponse ::= SEQUENCE {
// responseStatus OCSPResponseStatus,
// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
// 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
// }
SECItem* responseStatus = SECITEM_AllocItem(context.arena, nullptr, 3);
if (!responseStatus) {
return nullptr;
}
responseStatus->data[0] = der::ENUMERATED;
responseStatus->data[1] = 1;
responseStatus->data[2] = context.responseStatus;
SECItem* responseBytes = ResponseBytes(context);
if (!responseBytes) {
return nullptr;
}
SECItem* responseBytesNested = EncodeNested(context.arena,
der::CONSTRUCTED |
der::CONTEXT_SPECIFIC |
0,
responseBytes);
if (!responseBytesNested) {
return nullptr;
}
Output output;
if (output.Add(responseStatus) != der::Success) {
return nullptr;
}
if (output.Add(responseBytesNested) != der::Success) {
return nullptr;
}
return output.Squash(context.arena, der::SEQUENCE);
}
// ResponseBytes ::= SEQUENCE {
// responseType OBJECT IDENTIFIER,
// response OCTET STRING }
SECItem*
ResponseBytes(OCSPResponseContext& context)
{
// Includes tag and length
static const uint8_t id_pkix_ocsp_basic_encoded[] = {
0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
};
SECItem id_pkix_ocsp_basic = {
siBuffer,
const_cast<uint8_t*>(id_pkix_ocsp_basic_encoded),
PR_ARRAY_SIZE(id_pkix_ocsp_basic_encoded)
};
SECItem* response = BasicOCSPResponse(context);
if (!response) {
return nullptr;
}
SECItem* responseNested = EncodeNested(context.arena, der::OCTET_STRING,
response);
if (!responseNested) {
return nullptr;
}
Output output;
if (output.Add(&id_pkix_ocsp_basic) != der::Success) {
return nullptr;
}
if (output.Add(responseNested) != der::Success) {
return nullptr;
}
return output.Squash(context.arena, der::SEQUENCE);
}
// BasicOCSPResponse ::= SEQUENCE {
// tbsResponseData ResponseData,
// signatureAlgorithm AlgorithmIdentifier,
// signature BIT STRING,
// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
SECItem*
BasicOCSPResponse(OCSPResponseContext& context)
{
SECItem* tbsResponseData = ResponseData(context);
if (!tbsResponseData) {
return nullptr;
}
pkix::ScopedPtr<SECKEYPrivateKey, SECKEY_DestroyPrivateKey> privKey(
PK11_FindKeyByAnyCert(context.signerCert.get(), nullptr));
if (!privKey) {
return nullptr;
}
SECOidTag signatureAlgTag = SEC_GetSignatureAlgorithmOidTag(privKey->keyType,
SEC_OID_SHA1);
if (signatureAlgTag == SEC_OID_UNKNOWN) {
return nullptr;
}
SECItem* signatureAlgorithm = AlgorithmIdentifier(context.arena,
signatureAlgTag);
if (!signatureAlgorithm) {
return nullptr;
}
// SEC_SignData doesn't take an arena parameter, so we have to manage
// the memory allocated in signature.
SECItem signature;
if (SEC_SignData(&signature, tbsResponseData->data, tbsResponseData->len,
privKey.get(), signatureAlgTag) != SECSuccess)
{
return nullptr;
}
// We have to add a byte at the beginning indicating no unused bits.
// TODO: add ability to have signatures of bit length not divisible by 8,
// resulting in unused bits in the bitstring encoding
SECItem* prefixedSignature = SECITEM_AllocItem(context.arena, nullptr,
signature.len + 1);
if (!prefixedSignature) {
SECITEM_FreeItem(&signature, false);
return nullptr;
}
prefixedSignature->data[0] = 0;
memcpy(prefixedSignature->data + 1, signature.data, signature.len);
SECITEM_FreeItem(&signature, false);
if (context.badSignature) {
PR_ASSERT(prefixedSignature->len > 8);
prefixedSignature->data[8]++;
}
SECItem* signatureNested = EncodeNested(context.arena, der::BIT_STRING,
prefixedSignature);
if (!signatureNested) {
return nullptr;
}
// TODO(bug 980538): certificates
Output output;
if (output.Add(tbsResponseData) != der::Success) {
return nullptr;
}
if (output.Add(signatureAlgorithm) != der::Success) {
return nullptr;
}
if (output.Add(signatureNested) != der::Success) {
return nullptr;
}
return output.Squash(context.arena, der::SEQUENCE);
}
// ResponseData ::= SEQUENCE {
// version [0] EXPLICIT Version DEFAULT v1,
// responderID ResponderID,
// producedAt GeneralizedTime,
// responses SEQUENCE OF SingleResponse,
// responseExtensions [1] EXPLICIT Extensions OPTIONAL }
SECItem*
ResponseData(OCSPResponseContext& context)
{
SECItem* responderID = ResponderID(context);
if (!responderID) {
return nullptr;
}
SECItem* producedAtEncoded = PRTimeToEncodedTime(context.arena,
context.producedAt);
if (!producedAtEncoded) {
return nullptr;
}
SECItem* responses = SingleResponse(context);
if (!responses) {
return nullptr;
}
SECItem* responsesNested = EncodeNested(context.arena, der::SEQUENCE,
responses);
if (!responsesNested) {
return nullptr;
}
Output output;
if (output.Add(responderID) != der::Success) {
return nullptr;
}
if (output.Add(producedAtEncoded) != der::Success) {
return nullptr;
}
if (output.Add(responsesNested) != der::Success) {
return nullptr;
}
return output.Squash(context.arena, der::SEQUENCE);
}
// ResponderID ::= CHOICE {
// byName [1] Name,
// byKey [2] KeyHash }
// }
SECItem*
ResponderID(OCSPResponseContext& context)
{
SECItem* contents = nullptr;
if (context.responderIDType == OCSPResponseContext::ByName) {
contents = &context.signerCert->derSubject;
} else if (context.responderIDType == OCSPResponseContext::ByKeyHash) {
contents = KeyHash(context);
if (!contents) {
return nullptr;
}
} else {
return nullptr;
}
return EncodeNested(context.arena,
der::CONSTRUCTED |
der::CONTEXT_SPECIFIC |
context.responderIDType,
contents);
}
// KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
// -- (i.e., the SHA-1 hash of the value of the
// -- BIT STRING subjectPublicKey [excluding
// -- the tag, length, and number of unused
// -- bits] in the responder's certificate)
SECItem*
KeyHash(OCSPResponseContext& context)
{
return KeyHashHelper(context.arena, context.signerCert.get());
}
// SingleResponse ::= SEQUENCE {
// certID CertID,
// certStatus CertStatus,
// thisUpdate GeneralizedTime,
// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
// singleExtensions [1] EXPLICIT Extensions OPTIONAL }
SECItem*
SingleResponse(OCSPResponseContext& context)
{
SECItem* certID = CertID(context);
if (!certID) {
return nullptr;
}
SECItem* certStatus = CertStatus(context);
if (!certStatus) {
return nullptr;
}
SECItem* thisUpdateEncoded = PRTimeToEncodedTime(context.arena,
context.thisUpdate);
if (!thisUpdateEncoded) {
return nullptr;
}
SECItem* nextUpdateEncodedNested = nullptr;
if (context.includeNextUpdate) {
SECItem* nextUpdateEncoded = PRTimeToEncodedTime(context.arena,
context.nextUpdate);
if (!nextUpdateEncoded) {
return nullptr;
}
nextUpdateEncodedNested = EncodeNested(context.arena,
der::CONSTRUCTED |
der::CONTEXT_SPECIFIC |
0,
nextUpdateEncoded);
if (!nextUpdateEncodedNested) {
return nullptr;
}
}
Output output;
if (output.Add(certID) != der::Success) {
return nullptr;
}
if (output.Add(certStatus) != der::Success) {
return nullptr;
}
if (output.Add(thisUpdateEncoded) != der::Success) {
return nullptr;
}
if (nextUpdateEncodedNested) {
if (output.Add(nextUpdateEncodedNested) != der::Success) {
return nullptr;
}
}
return output.Squash(context.arena, der::SEQUENCE);
}
// CertID ::= SEQUENCE {
// hashAlgorithm AlgorithmIdentifier,
// issuerNameHash OCTET STRING, -- Hash of issuer's DN
// issuerKeyHash OCTET STRING, -- Hash of issuer's public key
// serialNumber CertificateSerialNumber }
SECItem*
CertID(OCSPResponseContext& context)
{
SECItem* hashAlgorithm = AlgorithmIdentifier(context.arena,
context.certIDHashAlg);
if (!hashAlgorithm) {
return nullptr;
}
SECItem* issuerNameHash = HashedOctetString(context.arena,
&context.issuerCert->derSubject,
context.certIDHashAlg);
if (!issuerNameHash) {
return nullptr;
}
SECItem* issuerKeyHash = KeyHashHelper(context.arena,
context.issuerCert.get());
if (!issuerKeyHash) {
return nullptr;
}
static const SEC_ASN1Template serialTemplate[] = {
{ SEC_ASN1_INTEGER, offsetof(CERTCertificate, serialNumber) },
{ 0 }
};
SECItem* serialNumber = SEC_ASN1EncodeItem(context.arena, nullptr,
context.cert.get(),
serialTemplate);
if (!serialNumber) {
return nullptr;
}
Output output;
if (output.Add(hashAlgorithm) != der::Success) {
return nullptr;
}
if (output.Add(issuerNameHash) != der::Success) {
return nullptr;
}
if (output.Add(issuerKeyHash) != der::Success) {
return nullptr;
}
if (output.Add(serialNumber) != der::Success) {
return nullptr;
}
return output.Squash(context.arena, der::SEQUENCE);
}
// CertStatus ::= CHOICE {
// good [0] IMPLICIT NULL,
// revoked [1] IMPLICIT RevokedInfo,
// unknown [2] IMPLICIT UnknownInfo }
//
// RevokedInfo ::= SEQUENCE {
// revocationTime GeneralizedTime,
// revocationReason [0] EXPLICIT CRLReason OPTIONAL }
//
// UnknownInfo ::= NULL
//
SECItem*
CertStatus(OCSPResponseContext& context)
{
switch (context.certStatus) {
// Both good and unknown are ultimately represented as NULL - the only
// difference is in the tag that identifies them.
case 0:
case 2:
{
SECItem* status = SECITEM_AllocItem(context.arena, nullptr, 2);
if (!status) {
return nullptr;
}
status->data[0] = der::CONTEXT_SPECIFIC | context.certStatus;
status->data[1] = 0;
return status;
}
case 1:
{
SECItem* revocationTime = PRTimeToEncodedTime(context.arena,
context.revocationTime);
if (!revocationTime) {
return nullptr;
}
// TODO(bug 980536): add support for revocationReason
return EncodeNested(context.arena,
der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 1,
revocationTime);
}
default:
PR_NOT_REACHED("CertStatus: bad context.certStatus");
PR_Abort();
}
return nullptr;
}
} } // namespace insanity::test

Просмотреть файл

@ -0,0 +1,65 @@
/* -*- 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.
*/
#ifndef insanity_test__pkixtestutils_h
#define insanity_test__pkixtestutils_h
#include "insanity/ScopedPtr.h"
#include "insanity/pkixtypes.h"
#include "seccomon.h"
namespace insanity { namespace test {
class OCSPResponseContext
{
public:
PLArenaPool* arena;
// TODO(bug 980538): add a way to specify what certificates are included.
pkix::ScopedCERTCertificate cert; // The subject of the OCSP response
pkix::ScopedCERTCertificate issuerCert; // The issuer of the subject
pkix::ScopedCERTCertificate signerCert; // This cert signs the response
uint8_t responseStatus; // See the OCSPResponseStatus enum in rfc 6960
// TODO(bug 979070): add ability to generate a response with no responseBytes
// The following fields are on a per-SingleResponse basis. In the future we
// may support including multiple SingleResponses per response.
PRTime producedAt;
PRTime thisUpdate;
PRTime nextUpdate;
bool includeNextUpdate;
SECOidTag certIDHashAlg;
uint8_t certStatus; // See the CertStatus choice in rfc 6960
PRTime revocationTime; // For certStatus == revoked
bool badSignature; // If true, alter the signature to fail verification
enum ResponderIDType {
ByName = 1,
ByKeyHash = 2
};
ResponderIDType responderIDType;
};
// The return value, if non-null, is owned by the arena in the context
// and MUST NOT be freed.
// This function does its best to respect the NSPR error code convention
// (that is, if it returns null, calling PR_GetError() will return the
// error of the failed operation). However, this is not guaranteed.
SECItem* CreateEncodedOCSPResponse(OCSPResponseContext& context);
} } // namespace insanity::test
#endif // insanity_test__pkixtestutils_h

Просмотреть файл

@ -44,6 +44,7 @@ const SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE = SEC_ERROR_BASE + 130;
const SEC_ERROR_OCSP_OLD_RESPONSE = SEC_ERROR_BASE + 132;
const SEC_ERROR_OCSP_INVALID_SIGNING_CERT = SEC_ERROR_BASE + 144;
const SEC_ERROR_POLICY_VALIDATION_FAILED = SEC_ERROR_BASE + 160; // -8032
const SEC_ERROR_OCSP_BAD_SIGNATURE = SEC_ERROR_BASE + 157;
const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED = SEC_ERROR_BASE + 176;
const SSL_ERROR_BAD_CERT_DOMAIN = SSL_ERROR_BASE + 12;

Просмотреть файл

@ -49,13 +49,9 @@ function add_tests_in_mode(useInsanity)
});
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
// the verification library to return a failure error code without calling
// PORT_SetError with the specific error, violating the expectations
// of the error handling code.
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_BAD_SIGNATURE));
add_connection_test("ocsp-stapling-none.example.com",
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_INVALID_SIGNING_CERT));
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_BAD_SIGNATURE));
add_test(function () {
// XXX(bug 915932): special case for insanity::pkix due to the temporary
// lack of an OCSP cache.

Просмотреть файл

@ -71,8 +71,10 @@ function add_tests_in_mode(useInsanity, certDB, otherTestCA) {
Ci.nsIX509CertDB.TRUSTED_SSL);
run_next_test();
});
// TODO(bug 979055): When using ByName instead of ByKey, the error here is
// SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE. We should be testing both cases.
add_ocsp_test("ocsp-stapling-good-other-ca.example.com",
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE),
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_INVALID_SIGNING_CERT),
true);
// TODO: Test the case where the signing cert can't be found at all, which

Просмотреть файл

@ -162,5 +162,3 @@ main(int argc, char* argv[])
}
return 0;
}

Просмотреть файл

@ -10,6 +10,8 @@ LIBS = \
$(NSPR_LIBS) \
$(NSS_LIBS) \
$(MOZALLOC_LIB) \
../../../../../../certverifier/$(LIB_PREFIX)certverifier.$(LIB_SUFFIX) \
../../../../../../insanity/test/lib/$(LIB_PREFIX)pkixtestutil.$(LIB_SUFFIX) \
../lib/$(LIB_PREFIX)tlsserver.$(LIB_SUFFIX) \
$(NULL)

Просмотреть файл

@ -6,8 +6,9 @@
#include <stdio.h>
#include "TLSServer.h"
#include "ScopedNSSTypes.h"
#include "TLSServer.h"
#include "pkixtestutil.h"
#include "secerr.h"
using namespace mozilla;
@ -26,181 +27,101 @@ GetOCSPResponseForType(OCSPResponseType aORT, CERTCertificate *aCert,
return nullptr;
}
if (aORT == ORTEmpty) {
SECItemArray* arr = SECITEM_AllocArray(aArena, nullptr, 1);
arr->items[0].data = nullptr;
arr->items[0].len = 0;
return arr;
}
PRTime now = PR_Now();
ScopedCERTOCSPCertID id(CERT_CreateOCSPCertID(aCert, now));
if (!id) {
PrintPRError("CERT_CreateOCSPCertID failed");
return nullptr;
}
PRTime nextUpdate = now + 10 * PR_USEC_PER_SEC;
PRTime oneDay = 60*60*24 * (PRTime)PR_USEC_PER_SEC;
PRTime expiredTime = now - oneDay;
PRTime oldNow = now - (8 * oneDay);
PRTime oldNextUpdate = oldNow + 10 * PR_USEC_PER_SEC;
CERTOCSPSingleResponse *sr = nullptr;
switch (aORT) {
case ORTGood:
case ORTGoodOtherCA:
case ORTBadSignature:
sr = CERT_CreateOCSPSingleResponseGood(aArena, id, now, &nextUpdate);
if (!sr) {
PrintPRError("CERT_CreateOCSPSingleResponseGood failed");
return nullptr;
}
id.forget(); // owned by sr now
break;
case ORTRevoked:
sr = CERT_CreateOCSPSingleResponseRevoked(aArena, id, now, &nextUpdate,
expiredTime, nullptr);
if (!sr) {
PrintPRError("CERT_CreateOCSPSingleResponseRevoked failed");
return nullptr;
}
id.forget(); // owned by sr now
break;
case ORTUnknown:
sr = CERT_CreateOCSPSingleResponseUnknown(aArena, id, now, &nextUpdate);
if (!sr) {
PrintPRError("CERT_CreateOCSPSingleResponseUnknown failed");
return nullptr;
}
id.forget(); // owned by sr now
break;
case ORTExpired:
case ORTExpiredFreshCA:
sr = CERT_CreateOCSPSingleResponseGood(aArena, id, oldNow, &oldNextUpdate);
if (!sr) {
PrintPRError("CERT_CreateOCSPSingleResponseGood failed");
return nullptr;
}
id.forget(); // owned by sr now
break;
case ORTGoodOtherCert:
{
ScopedCERTCertificate otherCert(
PK11_FindCertFromNickname(aAdditionalCertName, nullptr));
if (!otherCert) {
PrintPRError("PK11_FindCertFromNickname failed");
return nullptr;
}
insanity::test::OCSPResponseContext context;
context.arena = aArena;
context.cert = CERT_DupCertificate(aCert);
context.issuerCert = nullptr;
context.signerCert = nullptr;
context.responseStatus = 0;
ScopedCERTOCSPCertID otherID(CERT_CreateOCSPCertID(otherCert, now));
if (!otherID) {
PrintPRError("CERT_CreateOCSPCertID failed");
return nullptr;
}
sr = CERT_CreateOCSPSingleResponseGood(aArena, otherID, now, &nextUpdate);
if (!sr) {
PrintPRError("CERT_CreateOCSPSingleResponseGood failed");
return nullptr;
}
otherID.forget(); // owned by sr now
break;
}
case ORTEmpty:
case ORTMalformed:
case ORTSrverr:
case ORTTryLater:
case ORTNeedsSig:
case ORTUnauthorized:
break;
default:
if (gDebugLevel >= DEBUG_ERRORS) {
fprintf(stderr, "bad ocsp response type: %d\n", aORT);
}
return nullptr;
}
context.producedAt = now;
context.thisUpdate = now;
context.nextUpdate = now + 10 * PR_USEC_PER_SEC;
context.includeNextUpdate = true;
context.certIDHashAlg = SEC_OID_SHA1;
context.certStatus = 0;
context.revocationTime = 0;
context.badSignature = false;
context.responderIDType = insanity::test::OCSPResponseContext::ByKeyHash;
ScopedCERTCertificate ca;
if (aORT == ORTGoodOtherCA) {
ca = PK11_FindCertFromNickname(aAdditionalCertName, nullptr);
if (!ca) {
if (aORT == ORTGoodOtherCert) {
context.cert = PK11_FindCertFromNickname(aAdditionalCertName, nullptr);
if (!context.cert) {
PrintPRError("PK11_FindCertFromNickname failed");
return nullptr;
}
} else if (aORT == ORTBadSignature) {
// passing in a null responderCert to CERT_CreateEncodedOCSPSuccessResponse
// causes it to generate an invalid signature (by design, for testing).
ca = nullptr;
} else {
// XXX CERT_FindCertIssuer uses the old, deprecated path-building logic
ca = CERT_FindCertIssuer(aCert, now, certUsageSSLCA);
if (!ca) {
PrintPRError("CERT_FindCertIssuer failed");
}
// XXX CERT_FindCertIssuer uses the old, deprecated path-building logic
context.issuerCert = CERT_FindCertIssuer(aCert, now, certUsageSSLCA);
if (!context.issuerCert) {
PrintPRError("CERT_FindCertIssuer failed");
return nullptr;
}
if (aORT == ORTGoodOtherCA) {
context.signerCert = PK11_FindCertFromNickname(aAdditionalCertName,
nullptr);
if (!context.signerCert) {
PrintPRError("PK11_FindCertFromNickname failed");
return nullptr;
}
}
PRTime signTime = now;
if (aORT == ORTExpired) {
signTime = oldNow;
}
CERTOCSPSingleResponse **responses;
SECItem *response = nullptr;
switch (aORT) {
case ORTMalformed:
response = CERT_CreateEncodedOCSPErrorResponse(
aArena, SEC_ERROR_OCSP_MALFORMED_REQUEST);
if (!response) {
PrintPRError("CERT_CreateEncodedOCSPErrorResponse failed");
return nullptr;
}
context.responseStatus = 1;
break;
case ORTSrverr:
response = CERT_CreateEncodedOCSPErrorResponse(
aArena, SEC_ERROR_OCSP_SERVER_ERROR);
if (!response) {
PrintPRError("CERT_CreateEncodedOCSPErrorResponse failed");
return nullptr;
}
context.responseStatus = 2;
break;
case ORTTryLater:
response = CERT_CreateEncodedOCSPErrorResponse(
aArena, SEC_ERROR_OCSP_TRY_SERVER_LATER);
if (!response) {
PrintPRError("CERT_CreateEncodedOCSPErrorResponse failed");
return nullptr;
}
context.responseStatus = 3;
break;
case ORTNeedsSig:
response = CERT_CreateEncodedOCSPErrorResponse(
aArena, SEC_ERROR_OCSP_REQUEST_NEEDS_SIG);
if (!response) {
PrintPRError("CERT_CreateEncodedOCSPErrorResponse failed");
return nullptr;
}
context.responseStatus = 5;
break;
case ORTUnauthorized:
response = CERT_CreateEncodedOCSPErrorResponse(
aArena, SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST);
if (!response) {
PrintPRError("CERT_CreateEncodedOCSPErrorResponse failed");
return nullptr;
}
break;
case ORTEmpty:
context.responseStatus = 6;
break;
default:
// responses is contained in aArena and will be freed when aArena is
responses = PORT_ArenaNewArray(aArena, CERTOCSPSingleResponse *, 2);
if (!responses) {
PrintPRError("PORT_ArenaNewArray failed");
return nullptr;
}
responses[0] = sr;
responses[1] = nullptr;
response = CERT_CreateEncodedOCSPSuccessResponse(
aArena, ca, ocspResponderID_byName, signTime, responses, nullptr);
if (!response) {
PrintPRError("CERT_CreateEncodedOCSPSuccessResponse failed");
return nullptr;
}
// context.responseStatus is 0 in all other cases, and it has
// already been initialized, above.
break;
}
if (aORT == ORTExpired || aORT == ORTExpiredFreshCA) {
context.thisUpdate = oldNow;
context.nextUpdate = oldNow + 10 * PR_USEC_PER_SEC;
}
if (aORT == ORTRevoked) {
context.certStatus = 1;
}
if (aORT == ORTUnknown) {
context.certStatus = 2;
}
if (aORT == ORTBadSignature) {
context.badSignature = true;
}
SECItemArray *arr = SECITEM_AllocArray(aArena, nullptr, 1);
if (!context.signerCert) {
context.signerCert = CERT_DupCertificate(context.issuerCert.get());
}
SECItem* response = insanity::test::CreateEncodedOCSPResponse(context);
if (!response) {
PrintPRError("CreateEncodedOCSPResponse failed");
return nullptr;
}
SECItemArray* arr = SECITEM_AllocArray(aArena, nullptr, 1);
arr->items[0].data = response ? response->data : nullptr;
arr->items[0].len = response ? response->len : 0;

Просмотреть файл

@ -9,4 +9,9 @@ UNIFIED_SOURCES += [
'TLSServer.cpp',
]
LOCAL_INCLUDES += [
'../../../../../../insanity/include',
'../../../../../../insanity/test/lib',
]
LIBRARY_NAME = 'tlsserver'