--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -18,11 +18,17 @@ if not CONFIG['NSS_NO_EV_CERTS']:
UNIFIED_SOURCES += [
'ExtendedValidation.cpp',
]
LOCAL_INCLUDES += [
'../insanity/include',
]
+DIRS += [
+ '../insanity/test/lib',
+]
+
FAIL_ON_WARNINGS = True
+LIBRARY_NAME = 'certverifier'
+
FINAL_LIBRARY = 'xul'
new file mode 100644
--- /dev/null
+++ b/security/insanity/test/lib/moz.build
@@ -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
new file mode 100644
--- /dev/null
+++ b/security/insanity/test/lib/pkixtestutil.cpp
@@ -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
new file mode 100644
--- /dev/null
+++ b/security/insanity/test/lib/pkixtestutil.h
@@ -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
--- a/security/manager/ssl/tests/unit/head_psm.js
+++ b/security/manager/ssl/tests/unit/head_psm.js
@@ -39,16 +39,17 @@ const SEC_ERROR_OCSP_TRY_SERVER_LATER
const SEC_ERROR_OCSP_REQUEST_NEEDS_SIG = SEC_ERROR_BASE + 123;
const SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST = SEC_ERROR_BASE + 124;
const SEC_ERROR_OCSP_UNKNOWN_CERT = SEC_ERROR_BASE + 126; // -8066
const SEC_ERROR_OCSP_MALFORMED_RESPONSE = SEC_ERROR_BASE + 129;
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;
// Supported Certificate Usages
const certificateUsageSSLClient = 0x0001;
const certificateUsageSSLServer = 0x0002;
const certificateUsageSSLCA = 0x0008;
--- a/security/manager/ssl/tests/unit/test_ocsp_required.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_required.js
@@ -44,23 +44,19 @@ function add_tests_in_mode(useInsanity)
{
add_test(function () {
Services.prefs.setBoolPref("security.use_insanity_verification",
useInsanity);
run_next_test();
});
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.
do_check_eq(gOCSPRequestCount, useInsanity ? 2 : 1);
gOCSPRequestCount = 0;
run_next_test();
});
}
--- a/security/manager/ssl/tests/unit/test_ocsp_stapling.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_stapling.js
@@ -66,18 +66,20 @@ function add_tests_in_mode(useInsanity,
// The stapled response is from a CA that is trusted but did not issue the
// server's certificate.
add_test(function() {
certDB.setCertTrust(otherTestCA, Ci.nsIX509Cert.CA_CERT,
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
// will result in SEC_ERROR_BAD_DATABASE in the NSS classic case.
add_ocsp_test("ocsp-stapling-malformed.example.com",
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_MALFORMED_REQUEST), true);
add_ocsp_test("ocsp-stapling-srverr.example.com",
--- a/security/manager/ssl/tests/unit/tlsserver/cmd/GenerateOCSPResponse.cpp
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/GenerateOCSPResponse.cpp
@@ -157,10 +157,8 @@ main(int argc, char* argv[])
if (!WriteResponse(filename, &response->items[0])) {
PR_fprintf(PR_STDERR, "Failed to write file %s\n", filename);
exit(EXIT_FAILURE);
}
}
return 0;
}
-
-
--- a/security/manager/ssl/tests/unit/tlsserver/cmd/Makefile.in
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/Makefile.in
@@ -5,13 +5,15 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
include $(topsrcdir)/config/config.mk
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)
DEFINES += $(TK_CFLAGS)
--- a/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.cpp
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.cpp
@@ -1,18 +1,19 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "OCSPCommon.h"
#include <stdio.h>
+#include "ScopedNSSTypes.h"
#include "TLSServer.h"
-#include "ScopedNSSTypes.h"
+#include "pkixtestutil.h"
#include "secerr.h"
using namespace mozilla;
using namespace mozilla::test;
SECItemArray *
GetOCSPResponseForType(OCSPResponseType aORT, CERTCertificate *aCert,
@@ -21,188 +22,108 @@ GetOCSPResponseForType(OCSPResponseType
if (aORT == ORTNone) {
if (gDebugLevel >= DEBUG_WARNINGS) {
fprintf(stderr, "GetOCSPResponseForType called with type ORTNone, "
"which makes no sense.\n");
}
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");
+ PRTime oneDay = 60*60*24 * (PRTime)PR_USEC_PER_SEC;
+ PRTime oldNow = now - (8 * oneDay);
+
+ 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;
+ }
+ }
+ // 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;
}
- 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;
- }
-
- 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) {
+ context.signerCert = PK11_FindCertFromNickname(aAdditionalCertName,
+ nullptr);
+ if (!context.signerCert) {
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");
- 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;
return arr;
}
--- a/security/manager/ssl/tests/unit/tlsserver/lib/moz.build
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/moz.build
@@ -4,9 +4,14 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
UNIFIED_SOURCES += [
'OCSPCommon.cpp',
'TLSServer.cpp',
]
+LOCAL_INCLUDES += [
+ '../../../../../../insanity/include',
+ '../../../../../../insanity/test/lib',
+]
+
LIBRARY_NAME = 'tlsserver'