bug 700693 - OCSP stapling testing r=bsmith

This commit is contained in:
David Keeler 2013-06-20 11:41:41 -07:00
parent 67c9526728
commit 316600ee29
18 changed files with 986 additions and 4 deletions

View File

@ -4,8 +4,7 @@
# 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/.
DIRS += ['src', 'public']
TEST_DIRS += ['tests']
DIRS += ['src', 'public', 'tests']
MODULE = 'pipnss'

View File

@ -20,6 +20,7 @@
#include "sechash.h"
#include "secpkcs7.h"
#include "prerror.h"
#include "ocsp.h"
namespace mozilla {
@ -96,6 +97,9 @@ MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTName,
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTCertNicknames,
CERTCertNicknames,
CERT_FreeNicknames)
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTOCSPCertID,
CERTOCSPCertID,
CERT_DestroyOCSPCertID)
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCERTSubjectPublicKeyInfo,
CERTSubjectPublicKeyInfo,
SECKEY_DestroySubjectPublicKeyInfo)

View File

@ -4,6 +4,7 @@
# 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/.
DIRS += ['unit']
TEST_DIRS += ['mochitest']
MODULE = 'pipnss'

View File

@ -7,9 +7,10 @@
const { 'classes': Cc, 'interfaces': Ci, 'utils': Cu, 'results': Cr } = Components;
let { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
let { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
Cu.import("resource://gre/modules/FileUtils.jsm"); // XXX: tempScope?
Cu.import("resource://gre/modules/Services.jsm"); // XXX: tempScope?
let gIsWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
function readFile(file) {
let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
@ -25,3 +26,10 @@ function addCertFromFile(certdb, filename, trustString) {
let der = readFile(certFile);
certdb.addCert(der, trustString, null);
}
function getXPCOMStatusFromNSS(offset) {
let nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"]
.getService(Ci.nsINSSErrorsService);
let statusNSS = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE + offset;
return nssErrorsService.getXPCOMFromNSSError(statusNSS);
}

View File

@ -0,0 +1,10 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
DIRS += ['test_ocsp_stapling']
MODULE = 'pipnss'

View File

@ -0,0 +1,211 @@
/* 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/. */
"use strict";
// In which we connect to a number of domains (as faked by a server running
// locally) with and without OCSP stapling enabled to determine that good
// things happen and bad things don't.
let { Promise } = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {});
let { HttpServer } = Cu.import("resource://testing-common/httpd.js", {});
let gOCSPServerProcess = null;
let gHttpServer = null;
const REMOTE_PORT = 8443;
const CALLBACK_PORT = 8444;
function Connection(aHost) {
this.host = aHost;
let threadManager = Cc["@mozilla.org/thread-manager;1"]
.getService(Ci.nsIThreadManager);
this.thread = threadManager.currentThread;
this.defer = Promise.defer();
let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
.getService(Ci.nsISocketTransportService);
this.transport = sts.createTransport(["ssl"], 1, aHost, REMOTE_PORT, null);
this.transport.setEventSink(this, this.thread);
this.inputStream = null;
this.outputStream = null;
this.connected = false;
}
Connection.prototype = {
// nsITransportEventSink
onTransportStatus: function(aTransport, aStatus, aProgress, aProgressMax) {
if (!this.connected && aStatus == Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
this.connected = true;
this.outputStream.asyncWait(this, 0, 0, this.thread);
}
},
// nsIInputStreamCallback
onInputStreamReady: function(aStream) {
try {
// this will throw if the stream has been closed by an error
let str = NetUtil.readInputStreamToString(aStream, aStream.available());
do_check_eq(str, "0");
this.inputStream.close();
this.inputStream = null;
this.outputStream.close();
this.outputStream = null;
this.transport = null;
this.defer.resolve(Cr.NS_OK);
} catch (e) {
this.defer.resolve(e.result);
}
},
// nsIOutputStreamCallback
onOutputStreamReady: function(aStream) {
let sslSocketControl = this.transport.securityInfo
.QueryInterface(Ci.nsISSLSocketControl);
sslSocketControl.proxyStartSSL();
this.outputStream.write("0", 1);
let inStream = this.transport.openInputStream(0, 0, 0)
.QueryInterface(Ci.nsIAsyncInputStream);
this.inputStream = inStream;
this.inputStream.asyncWait(this, 0, 0, this.thread);
},
go: function() {
this.outputStream = this.transport.openOutputStream(0, 0, 0)
.QueryInterface(Ci.nsIAsyncOutputStream);
return this.defer.promise;
}
};
/* Returns a promise to connect to aHost that resolves to the result of that
* connection */
function connectTo(aHost) {
Services.prefs.setCharPref("network.dns.localDomains", aHost);
let connection = new Connection(aHost);
return connection.go();
}
function add_connection_test(aHost, aExpectedResult, aStaplingEnabled) {
add_test(function() {
Services.prefs.setBoolPref("security.ssl.enable_ocsp_stapling",
aStaplingEnabled);
do_test_pending();
connectTo(aHost).then(function(aResult) {
do_check_eq(aResult, aExpectedResult);
do_test_finished();
run_next_test();
});
});
}
function cleanup() {
gOCSPServerProcess.kill();
}
function run_test() {
do_get_profile();
let certdb = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB);
addCertFromFile(certdb, "test_ocsp_stapling/ocsp-ca.der", "CTu,u,u");
let directoryService = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
let envSvc = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
let greDir = directoryService.get("GreD", Ci.nsIFile);
envSvc.set("DYLD_LIBRARY_PATH", greDir.path);
envSvc.set("LD_LIBRARY_PATH", greDir.path);
envSvc.set("OCSP_SERVER_DEBUG_LEVEL", "3");
envSvc.set("OCSP_SERVER_CALLBACK_PORT", CALLBACK_PORT);
gHttpServer = new HttpServer();
gHttpServer.registerPathHandler("/", handleServerCallback);
gHttpServer.start(CALLBACK_PORT);
let serverBin = directoryService.get("CurProcD", Ci.nsILocalFile);
serverBin.append("OCSPStaplingServer" + (gIsWindows ? ".exe" : ""));
// If we're testing locally, the above works. If not, the server executable
// is in another location.
if (!serverBin.exists()) {
serverBin = directoryService.get("CurWorkD", Ci.nsILocalFile);
while (serverBin.path.indexOf("xpcshell") != -1) {
serverBin = serverBin.parent;
}
serverBin.append("bin");
serverBin.append("OCSPStaplingServer" + (gIsWindows ? ".exe" : ""));
}
do_check_true(serverBin.exists());
gOCSPServerProcess = Cc["@mozilla.org/process/util;1"]
.createInstance(Ci.nsIProcess);
gOCSPServerProcess.init(serverBin);
let ocspCertDir = directoryService.get("CurWorkD", Ci.nsILocalFile);
ocspCertDir.append("test_ocsp_stapling");
do_check_true(ocspCertDir.exists());
gOCSPServerProcess.run(false, [ocspCertDir.path], 1);
do_test_pending();
}
function handleServerCallback(aRequest, aResponse) {
aResponse.write("OK!");
aResponse.seizePower();
aResponse.finish();
run_test_body();
}
function run_test_body() {
// In the absence of OCSP stapling, these should actually all work.
add_connection_test("ocsp-stapling-good.example.com", Cr.NS_OK, false);
add_connection_test("ocsp-stapling-revoked.example.com", Cr.NS_OK, false);
add_connection_test("ocsp-stapling-good-other-ca.example.com", Cr.NS_OK, false);
add_connection_test("ocsp-stapling-malformed.example.com", Cr.NS_OK, false);
add_connection_test("ocsp-stapling-srverr.example.com", Cr.NS_OK, false);
add_connection_test("ocsp-stapling-trylater.example.com", Cr.NS_OK, false);
add_connection_test("ocsp-stapling-needssig.example.com", Cr.NS_OK, false);
add_connection_test("ocsp-stapling-unauthorized.example.com", Cr.NS_OK, false);
add_connection_test("ocsp-stapling-unknown.example.com", Cr.NS_OK, false);
add_connection_test("ocsp-stapling-good-other.example.com", Cr.NS_OK, false);
add_connection_test("ocsp-stapling-none.example.com", Cr.NS_OK, false);
add_connection_test("ocsp-stapling-expired.example.com", Cr.NS_OK, false);
add_connection_test("ocsp-stapling-expired-fresh-ca.example.com", Cr.NS_OK, false);
// Now test OCSP stapling
// The following error codes are defined in security/nss/lib/util/SECerrs.h
add_connection_test("ocsp-stapling-good.example.com", Cr.NS_OK, true);
// SEC_ERROR_REVOKED_CERTIFICATE = SEC_ERROR_BASE + 12
add_connection_test("ocsp-stapling-revoked.example.com", getXPCOMStatusFromNSS(12), true);
// This stapled response is from a CA that is untrusted and did not issue
// the server's certificate.
// SEC_ERROR_BAD_DATABASE = SEC_ERROR_BASE + 18
add_connection_test("ocsp-stapling-good-other-ca.example.com", getXPCOMStatusFromNSS(18), true);
// Now add that CA to the trusted database. It still should not be able
// to sign for the ocsp response.
add_test(function() {
let certdb = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB);
addCertFromFile(certdb, "test_ocsp_stapling/ocsp-other-ca.der", "CTu,u,u");
run_next_test();
});
// SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE = (SEC_ERROR_BASE + 130)
add_connection_test("ocsp-stapling-good-other-ca.example.com", getXPCOMStatusFromNSS(130), true);
// SEC_ERROR_OCSP_MALFORMED_REQUEST = (SEC_ERROR_BASE + 120)
add_connection_test("ocsp-stapling-malformed.example.com", getXPCOMStatusFromNSS(120), true);
// SEC_ERROR_OCSP_SERVER_ERROR = (SEC_ERROR_BASE + 121)
add_connection_test("ocsp-stapling-srverr.example.com", getXPCOMStatusFromNSS(121), true);
// SEC_ERROR_OCSP_TRY_SERVER_LATER = (SEC_ERROR_BASE + 122)
add_connection_test("ocsp-stapling-trylater.example.com", getXPCOMStatusFromNSS(122), true);
// SEC_ERROR_OCSP_REQUEST_NEEDS_SIG = (SEC_ERROR_BASE + 123)
add_connection_test("ocsp-stapling-needssig.example.com", getXPCOMStatusFromNSS(123), true);
// SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST = (SEC_ERROR_BASE + 124)
add_connection_test("ocsp-stapling-unauthorized.example.com", getXPCOMStatusFromNSS(124), true);
// SEC_ERROR_OCSP_UNKNOWN_CERT = (SEC_ERROR_BASE + 126)
add_connection_test("ocsp-stapling-unknown.example.com", getXPCOMStatusFromNSS(126), true);
add_connection_test("ocsp-stapling-good-other.example.com", getXPCOMStatusFromNSS(126), true);
// SEC_ERROR_OCSP_MALFORMED_RESPONSE = (SEC_ERROR_BASE + 129)
add_connection_test("ocsp-stapling-none.example.com", getXPCOMStatusFromNSS(129), true);
// SEC_ERROR_OCSP_OLD_RESPONSE = (SEC_ERROR_BASE + 132)
add_connection_test("ocsp-stapling-expired.example.com", getXPCOMStatusFromNSS(132), true);
add_connection_test("ocsp-stapling-expired-fresh-ca.example.com", getXPCOMStatusFromNSS(132), true);
do_register_cleanup(function() { gHttpServer.stop(cleanup); });
run_next_test();
do_test_finished();
}

View File

@ -0,0 +1,32 @@
# vim: noexpandtab ts=8 sw=8
#
# 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/.
DEPTH = @DEPTH@
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = @relativesrcdir@
FAIL_ON_WARNINGS := 1
include $(DEPTH)/config/autoconf.mk
CPPSRCS = \
OCSPStaplingServer.cpp \
$(NULL)
SIMPLE_PROGRAMS := $(CPPSRCS:.cpp=$(BIN_SUFFIX))
include $(topsrcdir)/config/config.mk
LIBS = \
$(NSPR_LIBS) \
$(NSS_LIBS) \
$(MOZALLOC_LIB) \
$(NULL)
DEFINES += $(TK_CFLAGS)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,589 @@
/* 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/. */
// This is a standalone server that delivers various stapled OCSP responses.
// The client is expected to connect, initiate an SSL handshake (with SNI
// to indicate which "server" to connect to), and verify the OCSP response.
// If all is good, the client then sends one encrypted byte and receives that
// same byte back.
// This server also has the ability to "call back" another process waiting on
// it. That is, when the server is all set up and ready to receive connections,
// it will connect to a specified port and issue a simple HTTP request.
#include <stdio.h>
#include "ScopedNSSTypes.h"
#include "nspr.h"
#include "nss.h"
#include "ocsp.h"
#include "ocspt.h"
#include "plarenas.h"
#include "prenv.h"
#include "prerror.h"
#include "prnetdb.h"
#include "prtime.h"
#include "ssl.h"
#include "secerr.h"
using namespace mozilla;
#define LISTEN_PORT 8443
#define DEBUG_ERRORS 1
#define DEBUG_WARNINGS 2
#define DEBUG_VERBOSE 3
uint32_t gDebugLevel = 0;
uint32_t gCallbackPort = 0;
enum OCSPStapleResponseType
{
OSRTNull = 0,
OSRTGood, // the certificate is good
OSRTRevoked, // the certificate has been revoked
OSRTUnknown, // the responder doesn't know if the cert is good
OSRTGoodOtherCert, // the response references a different certificate
OSRTGoodOtherCA, // the wrong CA has signed the response
OSRTExpired, // the signature on the response has expired
OSRTExpiredFreshCA, // fresh signature, but old validity period
OSRTNone, // no stapled response
OSRTMalformed, // the response from the responder was malformed
OSRTSrverr, // the response indicates there was a server error
OSRTTryLater, // the responder replied with "try again later"
OSRTNeedsSig, // the response needs a signature
OSRTUnauthorized // the responder is not authorized for this certificate
};
struct OCSPHost
{
const char *mHostName;
const char *mCertName;
OCSPStapleResponseType mOSRT;
};
const OCSPHost sOCSPHosts[] =
{
{ "ocsp-stapling-good.example.com", "good", OSRTGood },
{ "ocsp-stapling-revoked.example.com", "revoked", OSRTRevoked },
{ "ocsp-stapling-unknown.example.com", "unknown", OSRTUnknown },
{ "ocsp-stapling-good-other.example.com", "good-other", OSRTGoodOtherCert },
{ "ocsp-stapling-good-other-ca.example.com", "good-otherCA", OSRTGoodOtherCA },
{ "ocsp-stapling-expired.example.com", "expired", OSRTExpired },
{ "ocsp-stapling-expired-fresh-ca.example.com", "expired-freshCA", OSRTExpiredFreshCA },
{ "ocsp-stapling-none.example.com", "none", OSRTNone },
{ "ocsp-stapling-malformed.example.com", "malformed", OSRTMalformed },
{ "ocsp-stapling-srverr.example.com", "srverr", OSRTSrverr },
{ "ocsp-stapling-trylater.example.com", "trylater", OSRTTryLater },
{ "ocsp-stapling-needssig.example.com", "needssig", OSRTNeedsSig },
{ "ocsp-stapling-unauthorized.example.com", "unauthorized", OSRTUnauthorized },
{ nullptr, nullptr, OSRTNull }
};
struct Connection
{
const OCSPHost *mHost;
PRFileDesc *mSocket;
char mByte;
Connection(PRFileDesc *aSocket);
~Connection();
};
Connection::Connection(PRFileDesc *aSocket)
: mHost(nullptr)
, mSocket(aSocket)
, mByte(0)
{}
Connection::~Connection()
{
if (mSocket) {
PR_Close(mSocket);
}
}
void
PrintPRError(const char *aPrefix)
{
const char *err = PR_ErrorToName(PR_GetError());
if (err) {
if (gDebugLevel >= DEBUG_ERRORS) {
fprintf(stderr, "%s: %s\n", aPrefix, err);
}
} else {
if (gDebugLevel >= DEBUG_ERRORS) {
fprintf(stderr, "%s\n", aPrefix);
}
}
}
nsresult
SendAll(PRFileDesc *aSocket, const char *aData, size_t aDataLen)
{
if (gDebugLevel >= DEBUG_VERBOSE) {
fprintf(stderr, "sending '%s'\n", aData);
}
while (aDataLen > 0) {
int32_t bytesSent = PR_Send(aSocket, aData, aDataLen, 0,
PR_INTERVAL_NO_TIMEOUT);
if (bytesSent == -1) {
PrintPRError("PR_Send failed");
return NS_ERROR_FAILURE;
}
aDataLen -= bytesSent;
aData += bytesSent;
}
return NS_OK;
}
nsresult
ReplyToRequest(Connection *aConn)
{
// For debugging purposes, SendAll can print out what it's sending.
// So, any strings we give to it to send need to be null-terminated.
char buf[2] = { aConn->mByte, 0 };
return SendAll(aConn->mSocket, buf, 1);
}
const OCSPHost *
GetOcspHost(const char *aServerName)
{
const OCSPHost *host = sOCSPHosts;
while (host->mHostName != nullptr &&
strcmp(host->mHostName, aServerName) != 0) {
host++;
}
if (!host->mHostName) {
fprintf(stderr, "host '%s' not in pre-defined list\n", aServerName);
MOZ_CRASH();
return nullptr;
}
return host;
}
nsresult
SetupTLS(Connection *aConn, PRFileDesc *aModelSocket)
{
PRFileDesc *sslSocket = SSL_ImportFD(aModelSocket, aConn->mSocket);
if (!sslSocket) {
PrintPRError("SSL_ImportFD failed");
return NS_ERROR_FAILURE;
}
aConn->mSocket = sslSocket;
SSL_OptionSet(sslSocket, SSL_SECURITY, true);
SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_CLIENT, false);
SSL_OptionSet(sslSocket, SSL_HANDSHAKE_AS_SERVER, true);
SSL_ResetHandshake(sslSocket, /* asServer */ 1);
return NS_OK;
}
nsresult
ReadRequest(Connection *aConn)
{
int32_t bytesRead = PR_Recv(aConn->mSocket, &aConn->mByte, 1, 0,
PR_INTERVAL_NO_TIMEOUT);
if (bytesRead < 1) {
PrintPRError("PR_Recv failed");
return NS_ERROR_FAILURE;
} else {
if (gDebugLevel >= DEBUG_VERBOSE) {
fprintf(stderr, "read '0x%hhx'\n", aConn->mByte);
}
}
return NS_OK;
}
void
HandleConnection(PRFileDesc *aSocket, PRFileDesc *aModelSocket)
{
Connection conn(aSocket);
nsresult rv = SetupTLS(&conn, aModelSocket);
if (NS_SUCCEEDED(rv)) {
rv = ReadRequest(&conn);
}
if (NS_SUCCEEDED(rv)) {
rv = ReplyToRequest(&conn);
}
}
void
DoCallback()
{
ScopedPRFileDesc socket(PR_NewTCPSocket());
if (!socket) {
PrintPRError("PR_NewTCPSocket failed");
return;
}
PRNetAddr addr;
PR_InitializeNetAddr(PR_IpAddrLoopback, gCallbackPort, &addr);
if (PR_Connect(socket, &addr, PR_INTERVAL_NO_TIMEOUT) != PR_SUCCESS) {
PrintPRError("PR_Connect failed");
return;
}
const char *request = "GET / HTTP/1.0\r\n\r\n";
SendAll(socket, request, strlen(request));
char buf[4096];
memset(buf, 0, sizeof(buf));
int32_t bytesRead = PR_Recv(socket, buf, sizeof(buf) - 1, 0,
PR_INTERVAL_NO_TIMEOUT);
if (bytesRead < 1) {
PrintPRError("PR_Recv failed");
return;
}
fprintf(stderr, "%s\n", buf);
memset(buf, 0, sizeof(buf));
bytesRead = PR_Recv(socket, buf, sizeof(buf) - 1, 0, PR_INTERVAL_NO_TIMEOUT);
if (bytesRead < 1) {
PrintPRError("PR_Recv failed");
return;
}
fprintf(stderr, "%s\n", buf);
}
SECItemArray *
GetOCSPResponseForType(OCSPStapleResponseType aOSRT, CERTCertificate *aCert,
PLArenaPool *aArena)
{
PRTime now = PR_Now();
ScopedCERTOCSPCertID id(CERT_CreateOCSPCertID(aCert, now));
if (!id) {
PrintPRError("CERT_CreateOCSPCertID failed");
return nullptr;
}
PRTime nextUpdate = now + 10 * PR_USEC_PER_SEC;
PRTime oneDay = 60*60*24 * (PRTime)PR_USEC_PER_SEC;
PRTime expiredTime = now - oneDay;
PRTime oldNow = now - (8 * oneDay);
PRTime oldNextUpdate = oldNow + 10 * PR_USEC_PER_SEC;
ScopedCERTCertificate othercert(PK11_FindCertFromNickname("good", nullptr));
ScopedCERTOCSPCertID otherid(CERT_CreateOCSPCertID(othercert, now));
if (!otherid) {
PrintPRError("CERT_CreateOCSPCertID failed");
return nullptr;
}
CERTOCSPSingleResponse *sr = nullptr;
switch (aOSRT) {
case OSRTGood:
case OSRTGoodOtherCA:
sr = CERT_CreateOCSPSingleResponseGood(aArena, id, now, &nextUpdate);
if (!sr) {
PrintPRError("CERT_CreateOCSPSingleResponseGood failed");
return nullptr;
}
break;
case OSRTRevoked:
sr = CERT_CreateOCSPSingleResponseRevoked(aArena, id, now, &nextUpdate,
expiredTime, nullptr);
if (!sr) {
PrintPRError("CERT_CreateOCSPSingleResponseRevoked failed");
return nullptr;
}
break;
case OSRTUnknown:
sr = CERT_CreateOCSPSingleResponseUnknown(aArena, id, now, &nextUpdate);
if (!sr) {
PrintPRError("CERT_CreateOCSPSingleResponseUnknown failed");
return nullptr;
}
break;
case OSRTExpired:
case OSRTExpiredFreshCA:
sr = CERT_CreateOCSPSingleResponseGood(aArena, id, oldNow, &oldNextUpdate);
if (!sr) {
PrintPRError("CERT_CreateOCSPSingleResponseGood failed");
return nullptr;
}
break;
case OSRTGoodOtherCert:
sr = CERT_CreateOCSPSingleResponseGood(aArena, otherid, now, &nextUpdate);
if (!sr) {
PrintPRError("CERT_CreateOCSPSingleResponseGood failed");
return nullptr;
}
break;
case OSRTNone:
case OSRTMalformed:
case OSRTSrverr:
case OSRTTryLater:
case OSRTNeedsSig:
case OSRTUnauthorized:
break;
default:
if (gDebugLevel >= DEBUG_ERRORS) {
fprintf(stderr, "bad ocsp response type: %d\n", aOSRT);
}
break;
}
ScopedCERTCertificate ca;
if (aOSRT == OSRTGoodOtherCA) {
ca = PK11_FindCertFromNickname("otherCA", nullptr);
if (!ca) {
PrintPRError("PK11_FindCertFromNickname failed");
return 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 (aOSRT == OSRTExpired) {
signTime = oldNow;
}
CERTOCSPSingleResponse **responses;
SECItem *response = nullptr;
switch (aOSRT) {
case OSRTMalformed:
response = CERT_CreateEncodedOCSPErrorResponse(
aArena, SEC_ERROR_OCSP_MALFORMED_REQUEST);
if (!response) {
PrintPRError("CERT_CreateEncodedOCSPErrorResponse failed");
return nullptr;
}
break;
case OSRTSrverr:
response = CERT_CreateEncodedOCSPErrorResponse(
aArena, SEC_ERROR_OCSP_SERVER_ERROR);
if (!response) {
PrintPRError("CERT_CreateEncodedOCSPErrorResponse failed");
return nullptr;
}
break;
case OSRTTryLater:
response = CERT_CreateEncodedOCSPErrorResponse(
aArena, SEC_ERROR_OCSP_TRY_SERVER_LATER);
if (!response) {
PrintPRError("CERT_CreateEncodedOCSPErrorResponse failed");
return nullptr;
}
break;
case OSRTNeedsSig:
response = CERT_CreateEncodedOCSPErrorResponse(
aArena, SEC_ERROR_OCSP_REQUEST_NEEDS_SIG);
if (!response) {
PrintPRError("CERT_CreateEncodedOCSPErrorResponse failed");
return nullptr;
}
break;
case OSRTUnauthorized:
response = CERT_CreateEncodedOCSPErrorResponse(
aArena, SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST);
if (!response) {
PrintPRError("CERT_CreateEncodedOCSPErrorResponse failed");
return nullptr;
}
break;
case OSRTNone:
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;
}
break;
}
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;
}
int32_t
DoSNISocketConfig(PRFileDesc *aFd, const SECItem *aSrvNameArr,
uint32_t aSrvNameArrSize, void *aArg)
{
const OCSPHost *host = nullptr;
for (uint32_t i = 0; i < aSrvNameArrSize; i++) {
host = GetOcspHost((const char *)aSrvNameArr[i].data);
if (host) {
break;
}
}
if (!host) {
return SSL_SNI_SEND_ALERT;
}
if (gDebugLevel >= DEBUG_VERBOSE) {
fprintf(stderr, "found pre-defined host '%s'\n", host->mHostName);
}
ScopedCERTCertificate cert(PK11_FindCertFromNickname(host->mCertName, nullptr));
if (!cert) {
PrintPRError("PK11_FindCertFromNickname failed");
return SSL_SNI_SEND_ALERT;
}
ScopedSECKEYPrivateKey key(PK11_FindKeyByAnyCert(cert, nullptr));
if (!key) {
PrintPRError("PK11_FindKeyByAnyCert failed");
return SSL_SNI_SEND_ALERT;
}
SSLKEAType certKEA = NSS_FindCertKEAType(cert);
if (SSL_ConfigSecureServer(aFd, cert, key, certKEA) != SECSuccess) {
PrintPRError("SSL_ConfigSecureServer failed");
return SSL_SNI_SEND_ALERT;
}
PLArenaPool arena;
PL_InitArenaPool(&arena, "OCSP response", 1024, 0);
// response is contained by the arena - finishing the arena will free it
SECItemArray *response = GetOCSPResponseForType(host->mOSRT, cert, &arena);
if (!response) {
PL_FinishArenaPool(&arena);
return SSL_SNI_SEND_ALERT;
}
// SSL_SetStapledOCSPResponses makes a deep copy of response
SECStatus st = SSL_SetStapledOCSPResponses(aFd, response, certKEA);
PL_FinishArenaPool(&arena);
if (st != SECSuccess) {
PrintPRError("SSL_SetStapledOCSPResponses failed");
return SSL_SNI_SEND_ALERT;
}
return 0;
}
void
StartServer()
{
ScopedPRFileDesc serverSocket(PR_NewTCPSocket());
if (!serverSocket) {
PrintPRError("PR_NewTCPSocket failed");
return;
}
PRSocketOptionData socketOption;
socketOption.option = PR_SockOpt_Reuseaddr;
socketOption.value.reuse_addr = true;
PR_SetSocketOption(serverSocket, &socketOption);
PRNetAddr serverAddr;
PR_InitializeNetAddr(PR_IpAddrLoopback, LISTEN_PORT, &serverAddr);
if (PR_Bind(serverSocket, &serverAddr) != PR_SUCCESS) {
PrintPRError("PR_Bind failed");
return;
}
if (PR_Listen(serverSocket, 1) != PR_SUCCESS) {
PrintPRError("PR_Listen failed");
return;
}
ScopedPRFileDesc rawModelSocket(PR_NewTCPSocket());
if (!rawModelSocket) {
PrintPRError("PR_NewTCPSocket failed for rawModelSocket");
return;
}
ScopedPRFileDesc modelSocket(SSL_ImportFD(nullptr, rawModelSocket.forget()));
if (!modelSocket) {
PrintPRError("SSL_ImportFD of rawModelSocket failed");
return;
}
if (SECSuccess != SSL_SNISocketConfigHook(modelSocket, DoSNISocketConfig,
nullptr)) {
PrintPRError("SSL_SNISocketConfigHook failed");
return;
}
// We have to configure the server with a certificate, but it's not one
// we're actually going to end up using. In the SNI callback, we pick
// the right certificate for the connection.
ScopedCERTCertificate cert(PK11_FindCertFromNickname("localhost", nullptr));
if (!cert) {
PrintPRError("PK11_FindCertFromNickname failed");
return;
}
ScopedSECKEYPrivateKey key(PK11_FindKeyByAnyCert(cert, nullptr));
if (!key) {
PrintPRError("PK11_FindKeyByAnyCert failed");
return;
}
SSLKEAType certKEA = NSS_FindCertKEAType(cert);
if (SSL_ConfigSecureServer(modelSocket, cert, key, certKEA) != SECSuccess) {
PrintPRError("SSL_ConfigSecureServer failed");
return;
}
if (gCallbackPort != 0) {
DoCallback();
}
while (true) {
PRNetAddr clientAddr;
PRFileDesc *clientSocket = PR_Accept(serverSocket, &clientAddr,
PR_INTERVAL_NO_TIMEOUT);
HandleConnection(clientSocket, modelSocket);
}
}
int
main(int argc, char *argv[])
{
const char *debugLevel = PR_GetEnv("OCSP_SERVER_DEBUG_LEVEL");
if (debugLevel) {
gDebugLevel = atoi(debugLevel);
}
const char *callbackPort = PR_GetEnv("OCSP_SERVER_CALLBACK_PORT");
if (callbackPort) {
gCallbackPort = atoi(callbackPort);
}
if (argc != 2) {
fprintf(stderr, "usage: %s <NSS DB directory>\n", argv[0]);
return 1;
}
if (NSS_Init(argv[1]) != SECSuccess) {
PrintPRError("NSS_Init failed");
return 1;
}
if (NSS_SetDomesticPolicy() != SECSuccess) {
PrintPRError("NSS_SetDomesticPolicy failed");
return 1;
}
if (SSL_ConfigServerSessionIDCache(0, 0, 0, nullptr) != SECSuccess) {
PrintPRError("SSL_ConfigServerSessionIDCache failed");
return 1;
}
StartServer();
return 0;
}

View File

@ -0,0 +1,117 @@
#!/bin/bash
#
# 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/.
#
# Usage: ./gen_ocsp_certs.sh <path to objdir> <output directory>
# e.g. (from the root of mozilla-central)
# `./security/manager/ssl/tests/unit/test_ocsp_stapling/gen_ocsp_certs.sh \
# obj-x86_64-unknown-linux-gnu/ \
# security/manager/ssl/tests/unit/test_ocsp_stapling/`
#
# NB: This will cause the following files to be overwritten if they are in
# the output directory:
# cert8.db, key3.db, secmod.db, ocsp-ca.der, ocsp-other-ca.der
if [ $# -ne 2 ]; then
echo "Usage: `basename ${0}` <path to objdir> <output directory>"
exit $E_BADARGS
fi
OBJDIR=${1}
OUTPUT_DIR=${2}
RUN_MOZILLA="$OBJDIR/dist/bin/run-mozilla.sh"
CERTUTIL="$OBJDIR/dist/bin/certutil"
function check_retval {
retval=$?
if [ "$retval" -ne 0 ]; then
echo "failed..."
exit "$retval"
fi
}
NOISE_FILE=`mktemp`
echo "running \"dd if=/dev/urandom of="$NOISE_FILE" bs=1024 count=8\""
dd if=/dev/urandom of="$NOISE_FILE" bs=1024 count=1
check_retval
PASSWORD_FILE=`mktemp`
function cleanup {
rm -f "$NOISE_FILE" "$PASSWORD_FILE"
}
if [ ! -f "$RUN_MOZILLA" ]; then
echo "Could not find run-mozilla.sh at \'$RUN_MOZILLA\'"
exit $E_BADARGS
fi
if [ ! -f "$CERTUTIL" ]; then
echo "Could not find certutil at \'$CERTUTIL\'"
exit $E_BADARGS
fi
if [ ! -d "$OUTPUT_DIR" ]; then
echo "Could not find output directory at \'$OUTPUT_DIR\'"
exit $E_BADARGS
fi
if [ -f "$OUTPUT_DIR/cert8.db" -o -f "$OUTPUT_DIR/key3.db" -o -f "$OUTPUT_DIR/secmod.db" ]; then
echo "Found pre-existing NSS DBs. Clobbering old OCSP certs."
rm -f "$OUTPUT_DIR/cert8.db" "$OUTPUT_DIR/key3.db" "$OUTPUT_DIR/secmod.db"
fi
echo "running \"$RUN_MOZILLA $CERTUTIL -d $OUTPUT_DIR -N -f $PASSWORD_FILE\""
$RUN_MOZILLA $CERTUTIL -d $OUTPUT_DIR -N -f $PASSWORD_FILE
check_retval
COMMON_ARGS="-v 360 -w -1 -2 -z $NOISE_FILE"
function make_CA {
CA_RESPONSES="y\n0\ny"
NICKNAME="${1}"
SUBJECT="${2}"
DERFILE="${3}"
check_retval
echo "running 'echo -e \"$CA_RESPONSES\" | $RUN_MOZILLA $CERTUTIL -d $OUTPUT_DIR -S -n $NICKNAME -s \"$SUBJECT\" -t CTu,u,u -x $COMMON_ARGS'"
echo -e "$CA_RESPONSES" | $RUN_MOZILLA $CERTUTIL -d $OUTPUT_DIR -S -n $NICKNAME -s "$SUBJECT" -t CTu,u,u -x $COMMON_ARGS
check_retval
echo "running \"$RUN_MOZILLA $CERTUTIL -d $OUTPUT_DIR -L -n $NICKNAME -r > $OUTPUT_DIR/$DERFILE\""
$RUN_MOZILLA $CERTUTIL -d $OUTPUT_DIR -L -n $NICKNAME -r > $OUTPUT_DIR/$DERFILE
check_retval
}
SERIALNO=1
function make_cert {
CERT_RESPONSES="n\n\ny"
NICKNAME="${1}"
SUBJECT="${2}"
CA="${3}"
echo "running 'echo -e \"$CERT_RESPONSES\" | $RUN_MOZILLA $CERTUTIL -d $OUTPUT_DIR -S -s \"$SUBJECT\" -n $NICKNAME -c $CA -t Pu,u,u -m $SERIALNO $COMMON_ARGS'"
echo -e "$CERT_RESPONSES" | $RUN_MOZILLA $CERTUTIL -d $OUTPUT_DIR -S -s "$SUBJECT" -n $NICKNAME -c $CA -t Pu,u,u -m $SERIALNO $COMMON_ARGS
check_retval
SERIALNO=$(($SERIALNO + 1))
}
make_CA ocspTestCA 'CN=OCSP stapling test CA' ocsp-ca.der
make_CA otherCA 'CN=OCSP other test CA' ocsp-other-ca.der
make_cert localhost 'CN=localhost' ocspTestCA
make_cert good 'CN=ocsp-stapling-good.example.com' ocspTestCA
make_cert revoked 'CN=ocsp-stapling-revoked.example.com' ocspTestCA
make_cert unknown 'CN=ocsp-stapling-unknown.example.com' ocspTestCA
make_cert good-other 'CN=ocsp-stapling-good-other.example.com' ocspTestCA
make_cert good-otherCA 'CN=ocsp-stapling-good-other-ca.example.com' ocspTestCA
make_cert expired 'CN=ocsp-stapling-expired.example.com' ocspTestCA
make_cert expired-freshCA 'CN=ocsp-stapling-expired-fresh-ca.example.com' ocspTestCA
make_cert none 'CN=ocsp-stapling-none.example.com' ocspTestCA
make_cert malformed 'CN=ocsp-stapling-malformed.example.com' ocspTestCA
make_cert srverr 'CN=ocsp-stapling-srverr.example.com' ocspTestCA
make_cert trylater 'CN=ocsp-stapling-trylater.example.com' ocspTestCA
make_cert needssig 'CN=ocsp-stapling-needssig.example.com' ocspTestCA
make_cert unauthorized 'CN=ocsp-stapling-unauthorized.example.com' ocspTestCA
cleanup

View File

@ -0,0 +1,8 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
MODULE = 'pipnss'

View File

@ -16,3 +16,4 @@ skip-if = os == "android"
skip-if = os == "android"
[test_sts_preloadlist_perwindowpb.js]
[test_sts_preloadlist_selfdestruct.js]
[test_ocsp_stapling.js]

View File

@ -125,6 +125,7 @@ TEST_HARNESS_BINS := \
ssltunnel$(BIN_SUFFIX) \
certutil$(BIN_SUFFIX) \
pk12util$(BIN_SUFFIX) \
OCSPStaplingServer$(BIN_SUFFIX) \
fix_stack_using_bpsyms.py \
$(NULL)

View File

@ -507,6 +507,7 @@ NO_PKG_FILES += \
ssltunnel* \
certutil* \
pk12util* \
OCSPStaplingServer* \
winEmbed.exe \
chrome/chrome.rdf \
chrome/app-chrome.manifest \