Bug 1005198: Make it easy to create test certificates in GTest tests, r=keeler

--HG--
extra : rebase_source : 0b1ec263a5a1ce1856afb12f11ea4c35c2aa55d0
extra : histedit_source : 40a3a3fc1993de0fcdeb5593a1a1df4dc94832b8
This commit is contained in:
Brian Smith 2014-04-25 19:57:40 -07:00
parent bb877fbffa
commit 02c940dedf
3 changed files with 840 additions and 178 deletions

View File

@ -50,36 +50,41 @@ GetOCSPResponseForType(OCSPResponseType aORT, CERTCertificate *aCert,
}
}
// XXX CERT_FindCertIssuer uses the old, deprecated path-building logic
context.issuerCert = CERT_FindCertIssuer(aCert, now, certUsageSSLCA);
if (!context.issuerCert) {
ScopedCERTCertificate issuerCert(CERT_FindCertIssuer(aCert, now,
certUsageSSLCA));
if (!issuerCert) {
PrintPRError("CERT_FindCertIssuer failed");
return nullptr;
}
context.issuerNameDER = &issuerCert->derSubject;
context.issuerSPKI = &issuerCert->subjectPublicKeyInfo;
ScopedCERTCertificate signerCert;
if (aORT == ORTGoodOtherCA || aORT == ORTDelegatedIncluded ||
aORT == ORTDelegatedIncludedLast || aORT == ORTDelegatedMissing ||
aORT == ORTDelegatedMissingMultiple) {
context.signerCert = PK11_FindCertFromNickname(aAdditionalCertName,
nullptr);
if (!context.signerCert) {
signerCert = PK11_FindCertFromNickname(aAdditionalCertName, nullptr);
if (!signerCert) {
PrintPRError("PK11_FindCertFromNickname failed");
return nullptr;
}
}
const SECItem* certs[5] = { nullptr, nullptr, nullptr, nullptr, nullptr };
if (aORT == ORTDelegatedIncluded) {
context.includedCertificates[0] =
CERT_DupCertificate(context.signerCert.get());
certs[0] = &signerCert->derCert;
context.certs = certs;
}
if (aORT == ORTDelegatedIncludedLast || aORT == ORTDelegatedMissingMultiple) {
context.includedCertificates[0] =
CERT_DupCertificate(context.issuerCert.get());
context.includedCertificates[1] = CERT_DupCertificate(context.cert.get());
context.includedCertificates[2] =
CERT_DupCertificate(context.issuerCert.get());
certs[0] = &issuerCert->derCert;
certs[1] = &context.cert->derCert;
certs[2] = &issuerCert->derCert;
if (aORT != ORTDelegatedMissingMultiple) {
context.includedCertificates[3] =
CERT_DupCertificate(context.signerCert.get());
certs[3] = &signerCert->derCert;
}
context.certs = certs;
}
switch (aORT) {
case ORTMalformed:
context.responseStatus = 1;
@ -148,8 +153,13 @@ GetOCSPResponseForType(OCSPResponseType aORT, CERTCertificate *aCert,
context.includeEmptyExtensions = true;
}
if (!context.signerCert) {
context.signerCert = CERT_DupCertificate(context.issuerCert.get());
if (!signerCert) {
signerCert = CERT_DupCertificate(issuerCert.get());
}
context.signerPrivateKey = PK11_FindKeyByAnyCert(signerCert.get(), nullptr);
if (!context.signerPrivateKey) {
PrintPRError("PK11_FindKeyByAnyCert failed");
return nullptr;
}
SECItem* response = CreateEncodedOCSPResponse(context);

View File

@ -15,18 +15,77 @@
* limitations under the License.
*/
#include "pkixcheck.h"
#include "pkixder.h"
#include "pkixtestutil.h"
#include <cerrno>
#include <limits>
#include <new>
#include "cryptohi.h"
#include "hasht.h"
#include "pk11pub.h"
#include "pkixcheck.h"
#include "pkixder.h"
#include "prinit.h"
#include "prprf.h"
#include "secder.h"
using namespace std;
namespace mozilla { namespace pkix { namespace test {
const PRTime ONE_DAY = PRTime(24) * PRTime(60) * PRTime(60) * PR_USEC_PER_SEC;
namespace {
inline void
deleteCharArray(char* chars)
{
delete[] chars;
}
} // unnamed namespace
FILE*
OpenFile(const char* dir, const char* filename, const char* mode)
{
PR_ASSERT(dir);
PR_ASSERT(*dir);
PR_ASSERT(filename);
PR_ASSERT(*filename);
ScopedPtr<char, deleteCharArray>
path(new (nothrow) char[strlen(dir) + 1 + strlen(filename) + 1]);
if (!path) {
PR_SetError(SEC_ERROR_NO_MEMORY, 0);
return nullptr;
}
strcpy(path.get(), dir);
strcat(path.get(), "/");
strcat(path.get(), filename);
ScopedFILE file;
#ifdef _MSC_VER
{
FILE* rawFile;
errno_t error = fopen_s(&rawFile, path.get(), mode);
if (error) {
// TODO: map error to NSPR error code
PR_SetError(PR_FILE_NOT_FOUND_ERROR, error);
rawFile = nullptr;
}
file = rawFile;
}
#else
file = fopen(path.get(), mode);
if (!file) {
// TODO: map errno to NSPR error code
PR_SetError(PR_FILE_NOT_FOUND_ERROR, errno);
}
#endif
return file.release();
}
class Output
{
public:
@ -102,7 +161,7 @@ private:
}
}
static const size_t MaxSequenceItems = 5;
static const size_t MaxSequenceItems = 10;
const SECItem* contents[MaxSequenceItems];
size_t numItems;
size_t length;
@ -116,25 +175,24 @@ OCSPResponseContext::OCSPResponseContext(PLArenaPool* arena,
PRTime time)
: arena(arena)
, cert(CERT_DupCertificate(cert))
, issuerCert(nullptr)
, signerCert(nullptr)
, responseStatus(0)
, responseStatus(successful)
, skipResponseBytes(false)
, issuerNameDER(nullptr)
, issuerSPKI(nullptr)
, signerNameDER(nullptr)
, producedAt(time)
, extensions(nullptr)
, includeEmptyExtensions(false)
, badSignature(false)
, certs(nullptr)
, certIDHashAlg(SEC_OID_SHA1)
, certStatus(good)
, revocationTime(0)
, thisUpdate(time)
, nextUpdate(time + 10 * PR_USEC_PER_SEC)
, includeNextUpdate(true)
, certIDHashAlg(SEC_OID_SHA1)
, certStatus(0)
, revocationTime(0)
, badSignature(false)
, responderIDType(ByKeyHash)
, extensions(nullptr)
, includeEmptyExtensions(false)
{
for (size_t i = 0; i < MaxIncludedCertificates; i++) {
includedCertificates[i] = nullptr;
}
}
static SECItem* ResponseBytes(OCSPResponseContext& context);
@ -145,10 +203,9 @@ static SECItem* KeyHash(OCSPResponseContext& context);
static SECItem* SingleResponse(OCSPResponseContext& context);
static SECItem* CertID(OCSPResponseContext& context);
static SECItem* CertStatus(OCSPResponseContext& context);
static SECItem* Certificates(OCSPResponseContext& context);
static SECItem*
EncodeNested(PLArenaPool* arena, uint8_t tag, SECItem* inner)
EncodeNested(PLArenaPool* arena, uint8_t tag, const SECItem* inner)
{
Output output;
if (output.Add(inner) != der::Success) {
@ -198,10 +255,10 @@ HashedOctetString(PLArenaPool* arena, const SECItem* bytes, SECOidTag hashAlg)
}
static SECItem*
KeyHashHelper(PLArenaPool* arena, const CERTCertificate* cert)
KeyHashHelper(PLArenaPool* arena, const CERTSubjectPublicKeyInfo* spki)
{
// We only need a shallow copy here.
SECItem spk = cert->subjectPublicKeyInfo.subjectPublicKey;
SECItem spk = spki->subjectPublicKey;
DER_ConvertBitString(&spk); // bits to bytes
return HashedOctetString(arena, &spk, SEC_OID_SHA1);
}
@ -229,24 +286,597 @@ AlgorithmIdentifier(PLArenaPool* arena, SECOidTag algTag)
}
static SECItem*
PRTimeToEncodedTime(PLArenaPool* arena, PRTime time)
BitString(PLArenaPool* arena, const SECItem* rawBytes, bool corrupt)
{
SECItem derTime;
if (DER_TimeToGeneralizedTimeArena(arena, &derTime, time) != SECSuccess) {
// We have to add a byte at the beginning indicating no unused bits.
// TODO: add ability to have bit strings of bit length not divisible by 8,
// resulting in unused bits in the bitstring encoding
SECItem* prefixed = SECITEM_AllocItem(arena, nullptr, rawBytes->len + 1);
if (!prefixed) {
return nullptr;
}
return EncodeNested(arena, der::GENERALIZED_TIME, &derTime);
prefixed->data[0] = 0;
memcpy(prefixed->data + 1, rawBytes->data, rawBytes->len);
if (corrupt) {
PR_ASSERT(prefixed->len > 8);
prefixed->data[8]++;
}
return EncodeNested(arena, der::BIT_STRING, prefixed);
}
static SECItem*
Boolean(PLArenaPool* arena, bool value)
{
PR_ASSERT(arena);
SECItem* result(SECITEM_AllocItem(arena, nullptr, 3));
if (!result) {
return nullptr;
}
result->data[0] = der::BOOLEAN;
result->data[1] = 1; // length
result->data[2] = value ? 0xff : 0x00;
return result;
}
static SECItem*
Integer(PLArenaPool* arena, long value)
{
if (value < 0 || value > 127) {
// TODO: add encoding of larger values
PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
return nullptr;
}
SECItem* encoded = SECITEM_AllocItem(arena, nullptr, 3);
if (!encoded) {
return nullptr;
}
encoded->data[0] = der::INTEGER;
encoded->data[1] = 1; // length
encoded->data[2] = value;
return encoded;
}
static SECItem*
OID(PLArenaPool* arena, SECOidTag tag)
{
const SECOidData* extnIDData(SECOID_FindOIDByTag(tag));
if (!extnIDData) {
return nullptr;
}
return EncodeNested(arena, der::OIDTag, &extnIDData->oid);
}
enum TimeEncoding { UTCTime = 0, GeneralizedTime = 1 };
// http://tools.ietf.org/html/rfc5280#section-4.1.2.5
// UTCTime: YYMMDDHHMMSSZ (years 1950-2049 only)
// GeneralizedTime: YYYYMMDDHHMMSSZ
static SECItem*
PRTimeToEncodedTime(PLArenaPool* arena, PRTime time, TimeEncoding encoding)
{
PR_ASSERT(encoding == UTCTime || encoding == GeneralizedTime);
PRExplodedTime exploded;
PR_ExplodeTime(time, PR_GMTParameters, &exploded);
if (exploded.tm_sec >= 60) {
// round down for leap seconds
exploded.tm_sec = 59;
}
if (encoding == UTCTime &&
(exploded.tm_year < 1950 || exploded.tm_year >= 2050)) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return nullptr;
}
SECItem* derTime = SECITEM_AllocItem(arena, nullptr,
encoding == UTCTime ? 15 : 17);
if (!derTime) {
return nullptr;
}
size_t i = 0;
derTime->data[i++] = encoding == GeneralizedTime ? 0x18 : 0x17; // tag
derTime->data[i++] = static_cast<uint8_t>(derTime->len - 2); // length
if (encoding == GeneralizedTime) {
derTime->data[i++] = '0' + (exploded.tm_year / 1000);
derTime->data[i++] = '0' + ((exploded.tm_year % 1000) / 100);
}
derTime->data[i++] = '0' + ((exploded.tm_year % 100) / 10);
derTime->data[i++] = '0' + (exploded.tm_year % 10);
derTime->data[i++] = '0' + ((exploded.tm_month + 1) / 10);
derTime->data[i++] = '0' + ((exploded.tm_month + 1) % 10);
derTime->data[i++] = '0' + (exploded.tm_mday / 10);
derTime->data[i++] = '0' + (exploded.tm_mday % 10);
derTime->data[i++] = '0' + (exploded.tm_hour / 10);
derTime->data[i++] = '0' + (exploded.tm_hour % 10);
derTime->data[i++] = '0' + (exploded.tm_min / 10);
derTime->data[i++] = '0' + (exploded.tm_min % 10);
derTime->data[i++] = '0' + (exploded.tm_sec / 10);
derTime->data[i++] = '0' + (exploded.tm_sec % 10);
derTime->data[i++] = 'Z';
return derTime;
}
static SECItem*
PRTimeToGeneralizedTime(PLArenaPool* arena, PRTime time)
{
return PRTimeToEncodedTime(arena, time, GeneralizedTime);
}
// http://tools.ietf.org/html/rfc5280#section-4.1.2.5: "CAs conforming to this
// profile MUST always encode certificate validity dates through the year 2049
// as UTCTime; certificate validity dates in 2050 or later MUST be encoded as
// GeneralizedTime." (This is a special case of the rule that we must always
// use the shortest possible encoding.)
static SECItem*
PRTimeToTimeChoice(PLArenaPool* arena, PRTime time)
{
PRExplodedTime exploded;
PR_ExplodeTime(time, PR_GMTParameters, &exploded);
return PRTimeToEncodedTime(arena, time,
(exploded.tm_year >= 1950 && exploded.tm_year < 2050) ? UTCTime
: GeneralizedTime);
}
static SECItem*
SignedData(PLArenaPool* arena, const SECItem* tbsData,
SECKEYPrivateKey* privKey, SECOidTag hashAlg,
bool corrupt, /*optional*/ SECItem const* const* certs)
{
PR_ASSERT(arena);
PR_ASSERT(tbsData);
PR_ASSERT(privKey);
if (!arena || !tbsData || !privKey) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return nullptr;
}
SECOidTag signatureAlgTag = SEC_GetSignatureAlgorithmOidTag(privKey->keyType,
hashAlg);
if (signatureAlgTag == SEC_OID_UNKNOWN) {
return nullptr;
}
SECItem* signatureAlgorithm = AlgorithmIdentifier(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, tbsData->data, tbsData->len, privKey,
signatureAlgTag) != SECSuccess)
{
return nullptr;
}
// TODO: add ability to have signatures of bit length not divisible by 8,
// resulting in unused bits in the bitstring encoding
SECItem* signatureNested = BitString(arena, &signature, corrupt);
SECITEM_FreeItem(&signature, false);
if (!signatureNested) {
return nullptr;
}
SECItem* certsNested = nullptr;
if (certs) {
Output certsOutput;
while (*certs) {
certsOutput.Add(*certs);
++certs;
}
SECItem* certsSequence = certsOutput.Squash(arena, der::SEQUENCE);
if (!certsSequence) {
return nullptr;
}
certsNested = EncodeNested(arena,
der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0,
certsSequence);
if (!certsNested) {
return nullptr;
}
}
Output output;
if (output.Add(tbsData) != der::Success) {
return nullptr;
}
if (output.Add(signatureAlgorithm) != der::Success) {
return nullptr;
}
if (output.Add(signatureNested) != der::Success) {
return nullptr;
}
if (certsNested) {
if (output.Add(certsNested) != der::Success) {
return nullptr;
}
}
return output.Squash(arena, der::SEQUENCE);
}
// Extension ::= SEQUENCE {
// extnID OBJECT IDENTIFIER,
// critical BOOLEAN DEFAULT FALSE,
// extnValue OCTET STRING
// -- contains the DER encoding of an ASN.1 value
// -- corresponding to the extension type identified
// -- by extnID
// }
static SECItem*
Extension(PLArenaPool* arena, SECOidTag extnIDTag,
ExtensionCriticality criticality, Output& value)
{
PR_ASSERT(arena);
if (!arena) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return nullptr;
}
Output output;
const SECItem* extnID(OID(arena, extnIDTag));
if (!extnID) {
return nullptr;
}
if (output.Add(extnID) != der::Success) {
return nullptr;
}
if (criticality == ExtensionCriticality::Critical) {
SECItem* critical(Boolean(arena, true));
if (!output.Add(critical) != der::Success) {
return nullptr;
}
}
SECItem* extnValueBytes(value.Squash(arena, der::SEQUENCE));
if (!extnValueBytes) {
return nullptr;
}
SECItem* extnValue(EncodeNested(arena, der::OCTET_STRING, extnValueBytes));
if (!extnValue) {
return nullptr;
}
if (output.Add(extnValue) != der::Success) {
return nullptr;
}
return output.Squash(arena, der::SEQUENCE);
}
SECItem*
MaybeLogOutput(SECItem* result, const char* suffix)
{
PR_ASSERT(suffix);
if (!result) {
return nullptr;
}
// This allows us to more easily debug the generated output, by creating a
// file in the directory given by MOZILLA_PKIX_TEST_LOG_DIR for each
// NOT THREAD-SAFE!!!
const char* logPath = getenv("MOZILLA_PKIX_TEST_LOG_DIR");
if (logPath) {
static int counter = 0;
ScopedPtr<char, PR_smprintf_free>
filename(PR_smprintf("%u-%s.der", counter, suffix));
++counter;
if (filename) {
ScopedFILE file(OpenFile(logPath, filename.get(), "wb"));
if (file) {
(void) fwrite(result->data, result->len, 1, file.get());
}
}
}
return result;
}
///////////////////////////////////////////////////////////////////////////////
// Key Pairs
SECStatus
GenerateKeyPair(/*out*/ ScopedSECKEYPublicKey& publicKey,
/*out*/ ScopedSECKEYPrivateKey& privateKey)
{
ScopedPtr<PK11SlotInfo, PK11_FreeSlot> slot(PK11_GetInternalSlot());
if (!slot) {
return SECFailure;
}
PK11RSAGenParams params;
params.keySizeInBits = 2048;
params.pe = 3;
SECKEYPublicKey* publicKeyTemp = nullptr;
privateKey = PK11_GenerateKeyPair(slot.get(), CKM_RSA_PKCS_KEY_PAIR_GEN,
&params, &publicKeyTemp, false, true,
nullptr);
if (!privateKey) {
PR_ASSERT(!publicKeyTemp);
return SECFailure;
}
publicKey = publicKeyTemp;
PR_ASSERT(publicKey);
return SECSuccess;
}
///////////////////////////////////////////////////////////////////////////////
// Certificates
static SECItem* TBSCertificate(PLArenaPool* arena, long version,
long serialNumber, SECOidTag signature,
const SECItem* issuer, PRTime notBefore,
PRTime notAfter, const SECItem* subject,
const SECKEYPublicKey* subjectPublicKey,
/*optional*/ SECItem const* const* extensions);
// Certificate ::= SEQUENCE {
// tbsCertificate TBSCertificate,
// signatureAlgorithm AlgorithmIdentifier,
// signatureValue BIT STRING }
SECItem*
CreateEncodedCertificate(PLArenaPool* arena, long version,
SECOidTag signature, long serialNumber,
const SECItem* issuerNameDER, PRTime notBefore,
PRTime notAfter, const SECItem* subjectNameDER,
/*optional*/ SECItem const* const* extensions,
/*optional*/ SECKEYPrivateKey* issuerPrivateKey,
SECOidTag signatureHashAlg,
/*out*/ ScopedSECKEYPrivateKey& privateKey)
{
PR_ASSERT(arena);
PR_ASSERT(issuerNameDER);
PR_ASSERT(subjectNameDER);
if (!arena || !issuerNameDER || !subjectNameDER) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return nullptr;
}
ScopedSECKEYPublicKey publicKey;
if (GenerateKeyPair(publicKey, privateKey) != SECSuccess) {
return nullptr;
}
SECItem* tbsCertificate(TBSCertificate(arena, version, serialNumber,
signature, issuerNameDER, notBefore,
notAfter, subjectNameDER,
publicKey.get(), extensions));
if (!tbsCertificate) {
return nullptr;
}
return MaybeLogOutput(SignedData(arena, tbsCertificate,
issuerPrivateKey ? issuerPrivateKey
: privateKey.get(),
signatureHashAlg, false, nullptr), "cert");
}
// TBSCertificate ::= SEQUENCE {
// version [0] Version DEFAULT v1,
// serialNumber CertificateSerialNumber,
// signature AlgorithmIdentifier,
// issuer Name,
// validity Validity,
// subject Name,
// subjectPublicKeyInfo SubjectPublicKeyInfo,
// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
// -- If present, version MUST be v2 or v3
// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
// -- If present, version MUST be v2 or v3
// extensions [3] Extensions OPTIONAL
// -- If present, version MUST be v3 -- }
static SECItem*
TBSCertificate(PLArenaPool* arena, long versionValue,
long serialNumberValue, SECOidTag signatureOidTag,
const SECItem* issuer, PRTime notBeforeTime,
PRTime notAfterTime, const SECItem* subject,
const SECKEYPublicKey* subjectPublicKey,
/*optional*/ SECItem const* const* extensions)
{
PR_ASSERT(arena);
PR_ASSERT(issuer);
PR_ASSERT(subject);
PR_ASSERT(subjectPublicKey);
if (!arena || !issuer || !subject || !subjectPublicKey) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return nullptr;
}
Output output;
if (versionValue != der::v1) {
SECItem* versionInteger(Integer(arena, versionValue));
if (!versionInteger) {
return nullptr;
}
SECItem* version(EncodeNested(arena,
der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0,
versionInteger));
if (!version) {
return nullptr;
}
if (output.Add(version) != der::Success) {
return nullptr;
}
}
SECItem* serialNumber(Integer(arena, serialNumberValue));
if (!serialNumber) {
return nullptr;
}
if (output.Add(serialNumber) != der::Success) {
return nullptr;
}
SECItem* signature(AlgorithmIdentifier(arena, signatureOidTag));
if (!signature) {
return nullptr;
}
if (output.Add(signature) != der::Success) {
return nullptr;
}
if (output.Add(issuer) != der::Success) {
return nullptr;
}
// Validity ::= SEQUENCE {
// notBefore Time,
// notAfter Time }
SECItem* validity;
{
SECItem* notBefore(PRTimeToTimeChoice(arena, notBeforeTime));
if (!notBefore) {
return nullptr;
}
SECItem* notAfter(PRTimeToTimeChoice(arena, notAfterTime));
if (!notAfter) {
return nullptr;
}
Output validityOutput;
if (validityOutput.Add(notBefore) != der::Success) {
return nullptr;
}
if (validityOutput.Add(notAfter) != der::Success) {
return nullptr;
}
validity = validityOutput.Squash(arena, der::SEQUENCE);
if (!validity) {
return nullptr;
}
}
if (output.Add(validity) != der::Success) {
return nullptr;
}
if (output.Add(subject) != der::Success) {
return nullptr;
}
// SubjectPublicKeyInfo ::= SEQUENCE {
// algorithm AlgorithmIdentifier,
// subjectPublicKey BIT STRING }
ScopedSECItem subjectPublicKeyInfo(
SECKEY_EncodeDERSubjectPublicKeyInfo(subjectPublicKey));
if (!subjectPublicKeyInfo) {
return nullptr;
}
if (output.Add(subjectPublicKeyInfo.get()) != der::Success) {
return nullptr;
}
if (extensions) {
Output extensionsOutput;
while (*extensions) {
if (extensionsOutput.Add(*extensions) != der::Success) {
return nullptr;
}
++extensions;
}
SECItem* allExtensions(extensionsOutput.Squash(arena, der::SEQUENCE));
if (!allExtensions) {
return nullptr;
}
SECItem* extensionsWrapped(
EncodeNested(arena, der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 3,
allExtensions));
if (!extensions) {
return nullptr;
}
if (output.Add(extensionsWrapped) != der::Success) {
return nullptr;
}
}
return output.Squash(arena, der::SEQUENCE);
}
// BasicConstraints ::= SEQUENCE {
// cA BOOLEAN DEFAULT FALSE,
// pathLenConstraint INTEGER (0..MAX) OPTIONAL }
SECItem*
CreateEncodedBasicConstraints(PLArenaPool* arena, bool isCA,
long pathLenConstraintValue,
ExtensionCriticality criticality)
{
PR_ASSERT(arena);
if (!arena) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return nullptr;
}
Output value;
if (isCA) {
if (value.Add(Boolean(arena, true)) != der::Success) {
return nullptr;
}
}
SECItem* pathLenConstraint(Integer(arena, pathLenConstraintValue));
if (!pathLenConstraint) {
return nullptr;
}
if (value.Add(pathLenConstraint) != der::Success) {
return nullptr;
}
return Extension(arena, SEC_OID_X509_BASIC_CONSTRAINTS, criticality, value);
}
// ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
// KeyPurposeId ::= OBJECT IDENTIFIER
SECItem*
CreateEncodedEKUExtension(PLArenaPool* arena, SECOidTag const* ekus,
size_t ekusCount, ExtensionCriticality criticality)
{
PR_ASSERT(arena);
PR_ASSERT(ekus);
if (!arena || (!ekus && ekusCount != 0)) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return nullptr;
}
Output value;
for (size_t i = 0; i < ekusCount; ++i) {
SECItem* encodedEKUOID = OID(arena, ekus[i]);
if (!encodedEKUOID) {
return nullptr;
}
if (value.Add(encodedEKUOID) != der::Success) {
return nullptr;
}
}
return Extension(arena, SEC_OID_X509_EXT_KEY_USAGE, criticality, value);
}
///////////////////////////////////////////////////////////////////////////////
// OCSP responses
SECItem*
CreateEncodedOCSPResponse(OCSPResponseContext& context)
{
if (!context.arena || !context.cert || !context.issuerCert ||
!context.signerCert) {
if (!context.arena) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return nullptr;
}
if (!context.skipResponseBytes) {
if (!context.cert || !context.issuerNameDER || !context.issuerSPKI ||
!context.signerPrivateKey) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return nullptr;
}
}
// OCSPResponse ::= SEQUENCE {
// responseStatus OCSPResponseStatus,
// responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
@ -293,7 +923,7 @@ CreateEncodedOCSPResponse(OCSPResponseContext& context)
return nullptr;
}
}
return output.Squash(context.arena, der::SEQUENCE);
return MaybeLogOutput(output.Squash(context.arena, der::SEQUENCE), "ocsp");
}
// ResponseBytes ::= SEQUENCE {
@ -344,84 +974,10 @@ BasicOCSPResponse(OCSPResponseContext& context)
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;
}
SECItem* certificatesNested = nullptr;
if (context.includedCertificates[0]) {
SECItem* certificates = Certificates(context);
if (!certificates) {
return nullptr;
}
certificatesNested = EncodeNested(context.arena,
der::CONSTRUCTED |
der::CONTEXT_SPECIFIC |
0,
certificates);
if (!certificatesNested) {
return nullptr;
}
}
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;
}
if (certificatesNested) {
if (output.Add(certificatesNested) != der::Success) {
return nullptr;
}
}
return output.Squash(context.arena, der::SEQUENCE);
// TODO(bug 980538): certs
return SignedData(context.arena, tbsResponseData,
context.signerPrivateKey.get(), SEC_OID_SHA1,
context.badSignature, context.certs);
}
// Extension ::= SEQUENCE {
@ -499,8 +1055,8 @@ ResponseData(OCSPResponseContext& context)
if (!responderID) {
return nullptr;
}
SECItem* producedAtEncoded = PRTimeToEncodedTime(context.arena,
context.producedAt);
SECItem* producedAtEncoded = PRTimeToGeneralizedTime(context.arena,
context.producedAt);
if (!producedAtEncoded) {
return nullptr;
}
@ -543,22 +1099,23 @@ ResponseData(OCSPResponseContext& context)
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;
}
const SECItem* contents;
uint8_t responderIDType;
if (context.signerNameDER) {
contents = context.signerNameDER;
responderIDType = 1; // byName
} else {
contents = KeyHash(context);
responderIDType = 2; // byKey
}
if (!contents) {
return nullptr;
}
return EncodeNested(context.arena,
der::CONSTRUCTED |
der::CONTEXT_SPECIFIC |
context.responderIDType,
responderIDType,
contents);
}
@ -570,7 +1127,17 @@ ResponderID(OCSPResponseContext& context)
SECItem*
KeyHash(OCSPResponseContext& context)
{
return KeyHashHelper(context.arena, context.signerCert.get());
ScopedSECKEYPublicKey
signerPublicKey(SECKEY_ConvertToPublicKey(context.signerPrivateKey.get()));
if (!signerPublicKey) {
return nullptr;
}
ScopedPtr<CERTSubjectPublicKeyInfo, SECKEY_DestroySubjectPublicKeyInfo>
signerSPKI(SECKEY_CreateSubjectPublicKeyInfo(signerPublicKey.get()));
if (!signerSPKI) {
return nullptr;
}
return KeyHashHelper(context.arena, signerSPKI.get());
}
// SingleResponse ::= SEQUENCE {
@ -590,15 +1157,15 @@ SingleResponse(OCSPResponseContext& context)
if (!certStatus) {
return nullptr;
}
SECItem* thisUpdateEncoded = PRTimeToEncodedTime(context.arena,
context.thisUpdate);
SECItem* thisUpdateEncoded = PRTimeToGeneralizedTime(context.arena,
context.thisUpdate);
if (!thisUpdateEncoded) {
return nullptr;
}
SECItem* nextUpdateEncodedNested = nullptr;
if (context.includeNextUpdate) {
SECItem* nextUpdateEncoded = PRTimeToEncodedTime(context.arena,
context.nextUpdate);
SECItem* nextUpdateEncoded = PRTimeToGeneralizedTime(context.arena,
context.nextUpdate);
if (!nextUpdateEncoded) {
return nullptr;
}
@ -644,13 +1211,12 @@ CertID(OCSPResponseContext& context)
return nullptr;
}
SECItem* issuerNameHash = HashedOctetString(context.arena,
&context.issuerCert->derSubject,
context.issuerNameDER,
context.certIDHashAlg);
if (!issuerNameHash) {
return nullptr;
}
SECItem* issuerKeyHash = KeyHashHelper(context.arena,
context.issuerCert.get());
SECItem* issuerKeyHash = KeyHashHelper(context.arena, context.issuerSPKI);
if (!issuerKeyHash) {
return nullptr;
}
@ -711,8 +1277,8 @@ CertStatus(OCSPResponseContext& context)
}
case 1:
{
SECItem* revocationTime = PRTimeToEncodedTime(context.arena,
context.revocationTime);
SECItem* revocationTime = PRTimeToGeneralizedTime(context.arena,
context.revocationTime);
if (!revocationTime) {
return nullptr;
}
@ -728,19 +1294,4 @@ CertStatus(OCSPResponseContext& context)
return nullptr;
}
// SEQUENCE OF Certificate
SECItem*
Certificates(OCSPResponseContext& context)
{
Output output;
for (size_t i = 0; i < context.MaxIncludedCertificates; i++) {
CERTCertificate* cert = context.includedCertificates[i].get();
if (!cert) {
break;
}
output.Add(&cert->derCert);
}
return output.Squash(context.arena, der::SEQUENCE);
}
} } } // namespace mozilla::pkix::test

View File

@ -18,12 +18,86 @@
#ifndef mozilla_pkix_test__pkixtestutils_h
#define mozilla_pkix_test__pkixtestutils_h
#include "pkix/ScopedPtr.h"
#include <stdint.h>
#include <stdio.h>
#include "pkix/enumclass.h"
#include "pkix/pkixtypes.h"
#include "pkix/ScopedPtr.h"
#include "seccomon.h"
namespace mozilla { namespace pkix { namespace test {
namespace {
inline void
fclose_void(FILE* file) {
(void) fclose(file);
}
inline void
SECITEM_FreeItem_true(SECItem* item)
{
SECITEM_FreeItem(item, true);
}
} // unnamed namespace
typedef mozilla::pkix::ScopedPtr<FILE, fclose_void> ScopedFILE;
typedef mozilla::pkix::ScopedPtr<SECItem, SECITEM_FreeItem_true> ScopedSECItem;
typedef mozilla::pkix::ScopedPtr<SECKEYPrivateKey, SECKEY_DestroyPrivateKey>
ScopedSECKEYPrivateKey;
FILE* OpenFile(const char* dir, const char* filename, const char* mode);
extern const PRTime ONE_DAY;
SECStatus GenerateKeyPair(/*out*/ ScopedSECKEYPublicKey& publicKey,
/*out*/ ScopedSECKEYPrivateKey& privateKey);
///////////////////////////////////////////////////////////////////////////////
// Encode Certificates
enum Version { v1 = 0, v2 = 1, v3 = 2 };
// If extensions is null, then no extensions will be encoded. Otherwise,
// extensions must point to a null-terminated array of SECItem*. If the first
// item of the array is null then an empty Extensions sequence will be encoded.
//
// If issuerPrivateKey is null, then the certificate will be self-signed.
// Parameter order is based on the order of the attributes of the certificate
// in RFC 5280.
//
// The return value, if non-null, is owned by the arena in the context and
// MUST NOT be freed.
SECItem* CreateEncodedCertificate(PLArenaPool* arena, long version,
SECOidTag signature, long serialNumber,
const char* issuerASCII, PRTime notBefore,
PRTime notAfter, const char* subjectASCII,
/*optional*/ SECItem const* const* extensions,
/*optional*/ SECKEYPrivateKey* issuerPrivateKey,
SECOidTag signatureHashAlg,
/*out*/ ScopedSECKEYPrivateKey& privateKey);
MOZILLA_PKIX_ENUM_CLASS ExtensionCriticality { NotCritical = 0, Critical = 1 };
// The return value, if non-null, is owned by the arena and MUST NOT be freed.
SECItem* CreateEncodedBasicConstraints(PLArenaPool* arena, bool isCA,
long pathLenConstraint,
ExtensionCriticality criticality);
// ekus must be non-null and must must point to a SEC_OID_UNKNOWN-terminated
// array of SECOidTags. If the first item of the array is SEC_OID_UNKNOWN then
// an empty EKU extension will be encoded.
//
// The return value, if non-null, is owned by the arena and MUST NOT be freed.
SECItem* CreateEncodedEKUExtension(PLArenaPool* arena,
const SECOidTag* ekus, size_t ekusCount,
ExtensionCriticality criticality);
///////////////////////////////////////////////////////////////////////////////
// Encode OCSP responses
class OCSPResponseExtension
{
public:
@ -41,35 +115,62 @@ 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
// The fields below are in the order that they appear in an OCSP response.
// By directly using the issuer name & SPKI and signer name & private key,
// instead of extracting those things out of CERTCertificate objects, we
// avoid poor interactions with the NSS CERTCertificate caches. In
// particular, there are some tests in which it is important that we know
// that the issuer and/or signer certificates are NOT in the NSS caches
// because we ant to make sure that our path building logic will find them
// or we want to test what happens when those certificates cannot be found.
// This concern doesn't apply to |cert| above because our verification code
// for certificate chains and for OCSP responses take the end-entity cert
// as a CERTCertificate anyway.
enum OCSPResponseStatus {
successful = 0,
malformedRequest = 1,
internalError = 2,
tryLater = 3,
// 4 is not used
sigRequired = 5,
unauthorized = 6,
};
uint8_t responseStatus; // an OCSPResponseStatus or an invalid value
bool skipResponseBytes; // If true, don't include responseBytes
static const uint32_t MaxIncludedCertificates = 4;
pkix::ScopedCERTCertificate includedCertificates[MaxIncludedCertificates];
// responderID
const SECItem* issuerNameDER; // non-owning
const CERTSubjectPublicKeyInfo* issuerSPKI; // non-owning pointer
const SECItem* signerNameDER; // If set, responderID will use the byName
// form; otherwise responderID will use the
// byKeyHash form.
// 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;
OCSPResponseExtension* extensions;
bool includeEmptyExtensions; // If true, include the extension wrapper
// regardless of if there are any actual
// extensions.
ScopedSECKEYPrivateKey signerPrivateKey;
bool badSignature; // If true, alter the signature to fail verification
SECItem const* const* certs; // non-owning pointer to certs to embed
// The following fields are on a per-SingleResponse basis. In the future we
// may support including multiple SingleResponses per response.
SECOidTag certIDHashAlg;
enum CertStatus {
good = 0,
revoked = 1,
unknown = 2,
};
uint8_t certStatus; // CertStatus or an invalid value
PRTime revocationTime; // For certStatus == revoked
PRTime thisUpdate;
PRTime nextUpdate;
bool includeNextUpdate;
};
// The return value, if non-null, is owned by the arena in the context