Bug 974715 - Create more flexible OCSP response generation code. r=briansmith, r=cviecco
authorDavid Keeler <dkeeler@mozilla.com>
Mon, 10 Mar 2014 14:04:31 -0700
changeset 190142 ec47c16ac24d5c973c827358d9832dea0f68ee5f
parent 190141 64d45543afe4458e9f12749155515901cd697f26
child 190143 404edd4f2130a34d00ffbbe5491f94e34b5537fd
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbriansmith, cviecco
bugs974715
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 974715 - Create more flexible OCSP response generation code. r=briansmith, r=cviecco
security/certverifier/moz.build
security/insanity/test/lib/moz.build
security/insanity/test/lib/pkixtestutil.cpp
security/insanity/test/lib/pkixtestutil.h
security/manager/ssl/tests/unit/head_psm.js
security/manager/ssl/tests/unit/test_ocsp_required.js
security/manager/ssl/tests/unit/test_ocsp_stapling.js
security/manager/ssl/tests/unit/tlsserver/cmd/GenerateOCSPResponse.cpp
security/manager/ssl/tests/unit/tlsserver/cmd/Makefile.in
security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.cpp
security/manager/ssl/tests/unit/tlsserver/lib/moz.build
--- 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'