зеркало из https://github.com/mozilla/gecko-dev.git
Bug 974715 - Create more flexible OCSP response generation code. r=briansmith, r=cviecco
This commit is contained in:
Родитель
2bd5689ee7
Коммит
954d7d0bfb
|
@ -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) {
|
||||
insanity::test::OCSPResponseContext context;
|
||||
context.arena = aArena;
|
||||
context.cert = CERT_DupCertificate(aCert);
|
||||
context.issuerCert = nullptr;
|
||||
context.signerCert = nullptr;
|
||||
context.responseStatus = 0;
|
||||
|
||||
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;
|
||||
|
||||
if (aORT == ORTGoodOtherCert) {
|
||||
context.cert = PK11_FindCertFromNickname(aAdditionalCertName, nullptr);
|
||||
if (!context.cert) {
|
||||
PrintPRError("PK11_FindCertFromNickname failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ScopedCERTCertificate ca;
|
||||
if (aORT == ORTGoodOtherCA) {
|
||||
ca = PK11_FindCertFromNickname(aAdditionalCertName, nullptr);
|
||||
if (!ca) {
|
||||
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) {
|
||||
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'
|
||||
|
|
Загрузка…
Ссылка в новой задаче