diff options
Diffstat (limited to 'security/manager/ssl/tests/unit/tlsserver/cmd')
4 files changed, 465 insertions, 0 deletions
diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp b/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp new file mode 100644 index 000000000..348dcfc5c --- /dev/null +++ b/security/manager/ssl/tests/unit/tlsserver/cmd/BadCertServer.cpp @@ -0,0 +1,141 @@ +/* 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 uses various bad certificates. +// The client is expected to connect, initiate an SSL handshake (with SNI +// to indicate which "server" to connect to), and verify the certificate. +// 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 "TLSServer.h" + +using namespace mozilla; +using namespace mozilla::test; + +struct BadCertHost +{ + const char *mHostName; + const char *mCertName; +}; + +// Hostname, cert nickname pairs. +const BadCertHost sBadCertHosts[] = +{ + { "expired.example.com", "expired-ee" }, + { "notyetvalid.example.com", "notYetValid" }, + { "before-epoch.example.com", "beforeEpoch" }, + { "selfsigned.example.com", "selfsigned" }, + { "unknownissuer.example.com", "unknownissuer" }, + { "mismatch.example.com", "mismatch" }, + { "mismatch-CN.example.com", "mismatchCN" }, + { "expiredissuer.example.com", "expiredissuer" }, + { "notyetvalidissuer.example.com", "notYetValidIssuer" }, + { "before-epoch-issuer.example.com", "beforeEpochIssuer" }, + { "md5signature.example.com", "md5signature" }, + { "untrusted.example.com", "default-ee" }, + { "untrustedissuer.example.com", "untrustedissuer" }, + { "mismatch-expired.example.com", "mismatch-expired" }, + { "mismatch-notYetValid.example.com", "mismatch-notYetValid" }, + { "mismatch-untrusted.example.com", "mismatch-untrusted" }, + { "untrusted-expired.example.com", "untrusted-expired" }, + { "md5signature-expired.example.com", "md5signature-expired" }, + { "mismatch-untrusted-expired.example.com", "mismatch-untrusted-expired" }, + { "inadequatekeyusage.example.com", "inadequatekeyusage-ee" }, + { "selfsigned-inadequateEKU.example.com", "selfsigned-inadequateEKU" }, + { "self-signed-end-entity-with-cA-true.example.com", "self-signed-EE-with-cA-true" }, + { "ca-used-as-end-entity.example.com", "ca-used-as-end-entity" }, + { "ca-used-as-end-entity-name-mismatch.example.com", "ca-used-as-end-entity" }, + // All of include-subdomains.pinning.example.com is pinned to End Entity + // Test Cert with nick default-ee. Any other nick will only + // pass pinning when security.cert_pinning.enforcement.level != strict and + // otherCA is added as a user-specified trust anchor. See StaticHPKPins.h. + { "include-subdomains.pinning.example.com", "default-ee" }, + { "good.include-subdomains.pinning.example.com", "default-ee" }, + { "bad.include-subdomains.pinning.example.com", "other-issuer-ee" }, + { "bad.include-subdomains.pinning.example.com.", "other-issuer-ee" }, + { "bad.include-subdomains.pinning.example.com..", "other-issuer-ee" }, + { "exclude-subdomains.pinning.example.com", "default-ee" }, + { "sub.exclude-subdomains.pinning.example.com", "other-issuer-ee" }, + { "test-mode.pinning.example.com", "other-issuer-ee" }, + { "unknownissuer.include-subdomains.pinning.example.com", "unknownissuer" }, + { "unknownissuer.test-mode.pinning.example.com", "unknownissuer" }, + { "nsCertTypeNotCritical.example.com", "nsCertTypeNotCritical" }, + { "nsCertTypeCriticalWithExtKeyUsage.example.com", "nsCertTypeCriticalWithExtKeyUsage" }, + { "nsCertTypeCritical.example.com", "nsCertTypeCritical" }, + { "end-entity-issued-by-v1-cert.example.com", "eeIssuedByV1Cert" }, + { "end-entity-issued-by-non-CA.example.com", "eeIssuedByNonCA" }, + { "inadequate-key-size-ee.example.com", "inadequateKeySizeEE" }, + { "badSubjectAltNames.example.com", "badSubjectAltNames" }, + { "ipAddressAsDNSNameInSAN.example.com", "ipAddressAsDNSNameInSAN" }, + { "noValidNames.example.com", "noValidNames" }, + { "bug413909.xn--hxajbheg2az3al.xn--jxalpdlp", "idn-certificate" }, + { "emptyissuername.example.com", "emptyIssuerName" }, + { "ev-test.example.com", "ev-test" }, + { nullptr, nullptr } +}; + +int32_t +DoSNISocketConfigBySubjectCN(PRFileDesc* aFd, const SECItem* aSrvNameArr, + uint32_t aSrvNameArrSize) +{ + for (uint32_t i = 0; i < aSrvNameArrSize; i++) { + UniquePORTString name( + static_cast<char*>(PORT_ZAlloc(aSrvNameArr[i].len + 1))); + if (name) { + PORT_Memcpy(name.get(), aSrvNameArr[i].data, aSrvNameArr[i].len); + if (ConfigSecureServerWithNamedCert(aFd, name.get(), nullptr, nullptr) + == SECSuccess) { + return 0; + } + } + } + + return SSL_SNI_SEND_ALERT; +} + +int32_t +DoSNISocketConfig(PRFileDesc* aFd, const SECItem* aSrvNameArr, + uint32_t aSrvNameArrSize, void* aArg) +{ + const BadCertHost* host = GetHostForSNI(aSrvNameArr, aSrvNameArrSize, + sBadCertHosts); + if (!host) { + // No static cert <-> hostname mapping found. This happens when we use a + // collection of certificates in a given directory and build a cert DB at + // runtime, rather than using an NSS cert DB populated at build time. + // (This will be the default in the future.) + // For all given server names, check if the runtime-built cert DB contains + // a certificate with a matching subject CN. + return DoSNISocketConfigBySubjectCN(aFd, aSrvNameArr, aSrvNameArrSize); + } + + if (gDebugLevel >= DEBUG_VERBOSE) { + fprintf(stderr, "found pre-defined host '%s'\n", host->mHostName); + } + + UniqueCERTCertificate cert; + SSLKEAType certKEA; + if (SECSuccess != ConfigSecureServerWithNamedCert(aFd, host->mCertName, + &cert, &certKEA)) { + return SSL_SNI_SEND_ALERT; + } + + return 0; +} + +int +main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "usage: %s <NSS DB directory>\n", argv[0]); + return 1; + } + + return StartServer(argv[1], DoSNISocketConfig, nullptr); +} diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/GenerateOCSPResponse.cpp b/security/manager/ssl/tests/unit/tlsserver/cmd/GenerateOCSPResponse.cpp new file mode 100644 index 000000000..775f1f06e --- /dev/null +++ b/security/manager/ssl/tests/unit/tlsserver/cmd/GenerateOCSPResponse.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 tw=80 et: */ +/* 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 simple program takes a database directory, and one or more tuples like + * <typeOfResponse> <CertNick> <ExtraCertNick> <outPutFilename> + * to generate (one or more) ocsp responses. + */ + +#include <stdio.h> +#include <string> +#include <vector> + +#include "mozilla/ArrayUtils.h" + +#include "base64.h" +#include "cert.h" +#include "nspr.h" +#include "nss.h" +#include "plarenas.h" +#include "prerror.h" +#include "ssl.h" +#include "secerr.h" + +#include "OCSPCommon.h" +#include "ScopedNSSTypes.h" +#include "TLSServer.h" + +using namespace mozilla; +using namespace mozilla::test; + +struct OCSPResponseName +{ + const char *mTypeString; + const OCSPResponseType mORT; +}; + +const static OCSPResponseName kOCSPResponseNameList[] = { + { "good", ORTGood }, // the certificate is good + { "good-delegated", ORTDelegatedIncluded}, // the certificate is good, using + // a delegated signer + { "revoked", ORTRevoked}, // the certificate has been revoked + { "unknown", ORTUnknown}, // the responder doesn't know if the + // cert is good + { "goodotherca", ORTGoodOtherCA}, // the wrong CA has signed the + // response + { "expiredresponse", ORTExpired}, // the signature on the response has + // expired + { "oldvalidperiod", ORTExpiredFreshCA}, // fresh signature, but old validity + // period + { "empty", ORTEmpty}, // an empty stapled response + + { "malformed", ORTMalformed}, // the response from the responder + // was malformed + { "serverr", ORTSrverr}, // the response indicates there was a + // server error + { "trylater", ORTTryLater}, // the responder replied with + // "try again later" + { "resp-unsigned", ORTNeedsSig}, // the response needs a signature + { "unauthorized", ORTUnauthorized}, // the responder does not know about + // the cert + { "bad-signature", ORTBadSignature}, // the response has a bad signature + { "longvalidityalmostold", ORTLongValidityAlmostExpired}, // the response is + // still valid, but the generation + // is almost a year old + { "ancientstillvalid", ORTAncientAlmostExpired}, // The response is still + // valid but the generation is almost + // two years old +}; + +bool +StringToOCSPResponseType(const char* respText, + /*out*/ OCSPResponseType* OCSPType) +{ + if (!OCSPType) { + return false; + } + for (uint32_t i = 0; i < mozilla::ArrayLength(kOCSPResponseNameList); i++) { + if (strcmp(respText, kOCSPResponseNameList[i].mTypeString) == 0) { + *OCSPType = kOCSPResponseNameList[i].mORT; + return true; + } + } + return false; +} + +bool +WriteResponse(const char* filename, const SECItem* item) +{ + if (!filename || !item || !item->data) { + PR_fprintf(PR_STDERR, "invalid parameters to WriteResponse"); + return false; + } + + UniquePRFileDesc outFile(PR_Open(filename, + PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, + 0644)); + if (!outFile) { + PrintPRError("cannot open file for writing"); + return false; + } + int32_t rv = PR_Write(outFile.get(), item->data, item->len); + if (rv < 0 || (uint32_t) rv != item->len) { + PrintPRError("File write failure"); + return false; + } + + return true; +} + +int +main(int argc, char* argv[]) +{ + + if (argc < 6 || (argc - 6) % 4 != 0) { + PR_fprintf(PR_STDERR, "usage: %s <NSS DB directory> <responsetype> " + "<cert_nick> <extranick> <outfilename> [<resptype> " + "<cert_nick> <extranick> <outfilename>]* \n", + argv[0]); + exit(EXIT_FAILURE); + } + SECStatus rv = InitializeNSS(argv[1]); + if (rv != SECSuccess) { + PR_fprintf(PR_STDERR, "Failed to initialize NSS\n"); + exit(EXIT_FAILURE); + } + UniquePLArenaPool arena(PORT_NewArena(256 * argc)); + if (!arena) { + PrintPRError("PORT_NewArena failed"); + exit(EXIT_FAILURE); + } + + for (int i = 2; i + 3 < argc; i += 4) { + const char* ocspTypeText = argv[i]; + const char* certNick = argv[i + 1]; + const char* extraCertname = argv[i + 2]; + const char* filename = argv[i + 3]; + + OCSPResponseType ORT; + if (!StringToOCSPResponseType(ocspTypeText, &ORT)) { + PR_fprintf(PR_STDERR, "Cannot generate OCSP response of type %s\n", + ocspTypeText); + exit(EXIT_FAILURE); + } + + UniqueCERTCertificate cert(PK11_FindCertFromNickname(certNick, nullptr)); + if (!cert) { + PrintPRError("PK11_FindCertFromNickname failed"); + PR_fprintf(PR_STDERR, "Failed to find certificate with nick '%s'\n", + certNick); + exit(EXIT_FAILURE); + } + + SECItemArray* response = GetOCSPResponseForType(ORT, cert, arena, + extraCertname); + if (!response) { + PR_fprintf(PR_STDERR, "Failed to generate OCSP response of type %s " + "for %s\n", ocspTypeText, certNick); + exit(EXIT_FAILURE); + } + + if (!WriteResponse(filename, &response->items[0])) { + PR_fprintf(PR_STDERR, "Failed to write file %s\n", filename); + exit(EXIT_FAILURE); + } + } + return 0; +} diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/OCSPStaplingServer.cpp b/security/manager/ssl/tests/unit/tlsserver/cmd/OCSPStaplingServer.cpp new file mode 100644 index 000000000..6bf33a143 --- /dev/null +++ b/security/manager/ssl/tests/unit/tlsserver/cmd/OCSPStaplingServer.cpp @@ -0,0 +1,129 @@ +/* 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 "OCSPCommon.h" +#include "TLSServer.h" + +using namespace mozilla; +using namespace mozilla::test; + +const OCSPHost sOCSPHosts[] = +{ + { "ocsp-stapling-good.example.com", ORTGood, nullptr, nullptr }, + { "ocsp-stapling-revoked.example.com", ORTRevoked, nullptr, nullptr }, + { "ocsp-stapling-revoked-old.example.com", ORTRevokedOld, nullptr, nullptr }, + { "ocsp-stapling-unknown.example.com", ORTUnknown, nullptr, nullptr }, + { "ocsp-stapling-unknown-old.example.com", ORTUnknownOld, nullptr, nullptr }, + { "ocsp-stapling-good-other.example.com", ORTGoodOtherCert, "ocspOtherEndEntity", nullptr }, + { "ocsp-stapling-good-other-ca.example.com", ORTGoodOtherCA, "other-test-ca", nullptr }, + { "ocsp-stapling-expired.example.com", ORTExpired, nullptr, nullptr }, + { "ocsp-stapling-expired-fresh-ca.example.com", ORTExpiredFreshCA, nullptr, nullptr }, + { "ocsp-stapling-none.example.com", ORTNone, nullptr, nullptr }, + { "ocsp-stapling-empty.example.com", ORTEmpty, nullptr, nullptr }, + { "ocsp-stapling-malformed.example.com", ORTMalformed, nullptr, nullptr }, + { "ocsp-stapling-srverr.example.com", ORTSrverr, nullptr, nullptr }, + { "ocsp-stapling-trylater.example.com", ORTTryLater, nullptr, nullptr }, + { "ocsp-stapling-needssig.example.com", ORTNeedsSig, nullptr, nullptr }, + { "ocsp-stapling-unauthorized.example.com", ORTUnauthorized, nullptr, nullptr }, + { "ocsp-stapling-with-intermediate.example.com", ORTGood, nullptr, "ocspEEWithIntermediate" }, + { "ocsp-stapling-bad-signature.example.com", ORTBadSignature, nullptr, nullptr }, + { "ocsp-stapling-skip-responseBytes.example.com", ORTSkipResponseBytes, nullptr, nullptr }, + { "ocsp-stapling-critical-extension.example.com", ORTCriticalExtension, nullptr, nullptr }, + { "ocsp-stapling-noncritical-extension.example.com", ORTNoncriticalExtension, nullptr, nullptr }, + { "ocsp-stapling-empty-extensions.example.com", ORTEmptyExtensions, nullptr, nullptr }, + { "ocsp-stapling-delegated-included.example.com", ORTDelegatedIncluded, "delegatedSigner", nullptr }, + { "ocsp-stapling-delegated-included-last.example.com", ORTDelegatedIncludedLast, "delegatedSigner", nullptr }, + { "ocsp-stapling-delegated-missing.example.com", ORTDelegatedMissing, "delegatedSigner", nullptr }, + { "ocsp-stapling-delegated-missing-multiple.example.com", ORTDelegatedMissingMultiple, "delegatedSigner", nullptr }, + { "ocsp-stapling-delegated-no-extKeyUsage.example.com", ORTDelegatedIncluded, "invalidDelegatedSignerNoExtKeyUsage", nullptr }, + { "ocsp-stapling-delegated-from-intermediate.example.com", ORTDelegatedIncluded, "invalidDelegatedSignerFromIntermediate", nullptr }, + { "ocsp-stapling-delegated-keyUsage-crlSigning.example.com", ORTDelegatedIncluded, "invalidDelegatedSignerKeyUsageCrlSigning", nullptr }, + { "ocsp-stapling-delegated-wrong-extKeyUsage.example.com", ORTDelegatedIncluded, "invalidDelegatedSignerWrongExtKeyUsage", nullptr }, + { "ocsp-stapling-ancient-valid.example.com", ORTAncientAlmostExpired, nullptr, nullptr }, + { "keysize-ocsp-delegated.example.com", ORTDelegatedIncluded, "rsa-1016-keysizeDelegatedSigner", nullptr }, + { "revoked-ca-cert-used-as-end-entity.example.com", ORTRevoked, "ca-used-as-end-entity", nullptr }, + { "ocsp-stapling-must-staple.example.com", ORTGood, nullptr, "must-staple-ee" }, + { "ocsp-stapling-must-staple-revoked.example.com", ORTRevoked, nullptr, "must-staple-ee" }, + { "ocsp-stapling-must-staple-missing.example.com", ORTNone, nullptr, "must-staple-ee" }, + { "ocsp-stapling-must-staple-empty.example.com", ORTEmpty, nullptr, "must-staple-ee" }, + { "ocsp-stapling-must-staple-ee-with-must-staple-int.example.com", ORTGood, nullptr, "must-staple-ee-with-must-staple-int" }, + { "ocsp-stapling-plain-ee-with-must-staple-int.example.com", ORTGood, nullptr, "must-staple-missing-ee" }, + { "multi-tls-feature-good.example.com", ORTNone, nullptr, "multi-tls-feature-good-ee" }, + { "multi-tls-feature-bad.example.com", ORTNone, nullptr, "multi-tls-feature-bad-ee" }, + { nullptr, ORTNull, nullptr, nullptr } +}; + +int32_t +DoSNISocketConfig(PRFileDesc *aFd, const SECItem *aSrvNameArr, + uint32_t aSrvNameArrSize, void *aArg) +{ + const OCSPHost *host = GetHostForSNI(aSrvNameArr, aSrvNameArrSize, + sOCSPHosts); + if (!host) { + return SSL_SNI_SEND_ALERT; + } + + if (gDebugLevel >= DEBUG_VERBOSE) { + fprintf(stderr, "found pre-defined host '%s'\n", host->mHostName); + } + + const char *certNickname = host->mServerCertName ? host->mServerCertName + : DEFAULT_CERT_NICKNAME; + + UniqueCERTCertificate cert; + SSLKEAType certKEA; + if (SECSuccess != ConfigSecureServerWithNamedCert(aFd, certNickname, + &cert, &certKEA)) { + return SSL_SNI_SEND_ALERT; + } + + // If the OCSP response type is "none", don't staple a response. + if (host->mORT == ORTNone) { + return 0; + } + + UniquePLArenaPool arena(PORT_NewArena(1024)); + if (!arena) { + PrintPRError("PORT_NewArena failed"); + return SSL_SNI_SEND_ALERT; + } + + // response is contained by the arena - freeing the arena will free it + SECItemArray *response = GetOCSPResponseForType(host->mORT, cert, arena, + host->mAdditionalCertName); + if (!response) { + return SSL_SNI_SEND_ALERT; + } + + // SSL_SetStapledOCSPResponses makes a deep copy of response + SECStatus st = SSL_SetStapledOCSPResponses(aFd, response, certKEA); + if (st != SECSuccess) { + PrintPRError("SSL_SetStapledOCSPResponses failed"); + return SSL_SNI_SEND_ALERT; + } + + return 0; +} + +int +main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "usage: %s <NSS DB directory>\n", argv[0]); + return 1; + } + + return StartServer(argv[1], DoSNISocketConfig, nullptr); +} diff --git a/security/manager/ssl/tests/unit/tlsserver/cmd/moz.build b/security/manager/ssl/tests/unit/tlsserver/cmd/moz.build new file mode 100644 index 000000000..6fdd0c35b --- /dev/null +++ b/security/manager/ssl/tests/unit/tlsserver/cmd/moz.build @@ -0,0 +1,25 @@ +# -*- Mode: python; 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/. + +GeckoSimplePrograms([ + 'BadCertServer', + 'GenerateOCSPResponse', + 'OCSPStaplingServer', +], linkage=None) + +LOCAL_INCLUDES += [ + '../lib', +] + +USE_LIBS += [ + 'mozillapkix', + 'nspr', + 'nss', + 'pkixtestutil', + 'tlsserver', +] + +CXXFLAGS += CONFIG['TK_CFLAGS'] |