Bug 1125375 - Enhance tstclnt to offer TLS server chain diagnosis, r=rrelyea
authorKai Engert <kaie@kuix.de>
Sat, 21 Feb 2015 00:35:56 +0100
changeset 11362 aba37bd9510caef883ef9e8249411c998cec54d7
parent 11361 10c82beac9148ffb064d927e0e65df1ffe14b01c
child 11363 108bf1e59f285c3518e16a342654087c8615eba1
push id564
push userkaie@kuix.de
push dateFri, 20 Feb 2015 23:36:02 +0000
reviewersrrelyea
bugs1125375
Bug 1125375 - Enhance tstclnt to offer TLS server chain diagnosis, r=rrelyea
cmd/lib/secutil.c
cmd/lib/secutil.h
cmd/tstclnt/manifest.mn
cmd/tstclnt/tstclnt.c
--- a/cmd/lib/secutil.c
+++ b/cmd/lib/secutil.c
@@ -2407,16 +2407,56 @@ SECU_PrintCertificate(FILE *out, const S
 	secu_PrintDecodedBitString(out, &c->subjectID, "Subject Unique ID", level+1);
     SECU_PrintExtensions(out, c->extensions, "Signed Extensions", level+1);
 loser:
     PORT_FreeArena(arena, PR_FALSE);
     return rv;
 }
 
 int
+SECU_PrintCertificateBasicInfo(FILE *out, const SECItem *der, const char *m, int level)
+{
+    PLArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+    CERTCertificate *c;
+    int rv = SEC_ERROR_NO_MEMORY;
+    int iv;
+    
+    if (!arena)
+	return rv;
+
+    /* Decode certificate */
+    c = PORT_ArenaZNew(arena, CERTCertificate);
+    if (!c)
+	goto loser;
+    c->arena = arena;
+    rv = SEC_ASN1DecodeItem(arena, c, 
+                            SEC_ASN1_GET(CERT_CertificateTemplate), der);
+    if (rv) {
+        SECU_Indent(out, level); 
+	SECU_PrintErrMsg(out, level, "Error", "Parsing extension");
+	SECU_PrintAny(out, der, "Raw", level);
+	goto loser;
+    }
+    /* Pretty print it out */
+    SECU_Indent(out, level); fprintf(out, "%s:\n", m);
+    SECU_PrintInteger(out, &c->serialNumber, "Serial Number", level+1);
+    SECU_PrintAlgorithmID(out, &c->signature, "Signature Algorithm", level+1);
+    SECU_PrintName(out, &c->issuer, "Issuer", level+1);
+    if (!SECU_GetWrapEnabled()) /*SECU_PrintName didn't add newline*/
+	SECU_Newline(out);
+    secu_PrintValidity(out, &c->validity, "Validity", level+1);
+    SECU_PrintName(out, &c->subject, "Subject", level+1);
+    if (!SECU_GetWrapEnabled()) /*SECU_PrintName didn't add newline*/
+	SECU_Newline(out);
+loser:
+    PORT_FreeArena(arena, PR_FALSE);
+    return rv;
+}
+
+int
 SECU_PrintSubjectPublicKeyInfo(FILE *out, SECItem *der, char *m, int level)
 {
     PLArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
     int          rv    = SEC_ERROR_NO_MEMORY;
     CERTSubjectPublicKeyInfo spki;
 
     if (!arena)
 	return rv;
--- a/cmd/lib/secutil.h
+++ b/cmd/lib/secutil.h
@@ -216,16 +216,19 @@ int SECU_CheckCertNameExists(CERTCertDBH
 /* Dump contents of cert req */
 extern int SECU_PrintCertificateRequest(FILE *out, SECItem *der, char *m,
 	int level);
 
 /* Dump contents of certificate */
 extern int SECU_PrintCertificate(FILE *out, const SECItem *der, const char *m,
                                  int level);
 
+extern int SECU_PrintCertificateBasicInfo(FILE *out, const SECItem *der, const char *m,
+                                 int level);
+
 extern int SECU_PrintDumpDerIssuerAndSerial(FILE *out, SECItem *der, char *m,
                                  int level);
 
 /* Dump contents of a DER certificate name (issuer or subject) */
 extern int SECU_PrintDERName(FILE *out, SECItem *der, const char *m, int level);
 
 /* print trust flags on a cert */
 extern void SECU_PrintTrustFlags(FILE *out, CERTCertTrust *trust, char *m, 
--- a/cmd/tstclnt/manifest.mn
+++ b/cmd/tstclnt/manifest.mn
@@ -12,11 +12,12 @@ MODULE = nss
 # and gets translated into $LINCS in manifest.mnw
 # The MODULE is always implicitly required.
 # Listing it here in REQUIRES makes it appear twice in the cc command line.
 REQUIRES = seccmd dbm 
 
 # DIRS = 
 
 CSRCS	= tstclnt.c  
+DEFINES += -DDLL_PREFIX=\"$(DLL_PREFIX)\" -DDLL_SUFFIX=\"$(DLL_SUFFIX)\"
 
 PROGRAM	= tstclnt
 
--- a/cmd/tstclnt/tstclnt.c
+++ b/cmd/tstclnt/tstclnt.c
@@ -27,16 +27,17 @@
 #include "nspr.h"
 #include "prio.h"
 #include "prnetdb.h"
 #include "nss.h"
 #include "ocsp.h"
 #include "ssl.h"
 #include "sslproto.h"
 #include "pk11func.h"
+#include "secmod.h"
 #include "plgetopt.h"
 #include "plstr.h"
 
 #if defined(WIN32)
 #include <fcntl.h>
 #include <io.h>
 #endif
 
@@ -92,16 +93,17 @@ int ssl3CipherSuites[] = {
     TLS_DHE_RSA_WITH_AES_256_CBC_SHA,       	/* x */
     TLS_RSA_WITH_AES_256_CBC_SHA,     	    	/* y */
     TLS_RSA_WITH_NULL_SHA,			/* z */
     0
 };
 
 unsigned long __cmp_umuls;
 PRBool verbose;
+int dumpServerChain = 0;
 int renegotiationsToDo = 0;
 int renegotiationsDone = 0;
 
 static char *progName;
 
 secuPWData  pwdata          = { PW_NONE, 0 };
 
 void printSecurityInfo(PRFileDesc *fd)
@@ -174,33 +176,40 @@ handshakeCallback(PRFileDesc *fd, void *
 	++renegotiationsDone;
     }
 }
 
 static void PrintUsageHeader(const char *progName)
 {
     fprintf(stderr, 
 "Usage:  %s -h host [-a 1st_hs_name ] [-a 2nd_hs_name ] [-p port]\n"
-                    "[-d certdir] [-n nickname] [-Bafosvx] [-c ciphers] [-Y]\n"
+                    "[-D | -d certdir] [-C] [-b | -R root-module] \n"
+		    "[-n nickname] [-Bafosvx] [-c ciphers] [-Y]\n"
                     "[-V [min-version]:[max-version]] [-K] [-T]\n"
                     "[-r N] [-w passwd] [-W pwfile] [-q [-t seconds]]\n", 
             progName);
 }
 
 static void PrintParameterUsage(void)
 {
     fprintf(stderr, "%-20s Send different SNI name. 1st_hs_name - at first\n"
                     "%-20s handshake, 2nd_hs_name - at second handshake.\n"
                     "%-20s Default is host from the -h argument.\n", "-a name",
                     "", "");
     fprintf(stderr, "%-20s Hostname to connect with\n", "-h host");
     fprintf(stderr, "%-20s Port number for SSL server\n", "-p port");
     fprintf(stderr, 
             "%-20s Directory with cert database (default is ~/.netscape)\n",
 	    "-d certdir");
+    fprintf(stderr, "%-20s Run without a cert database\n", "-D");
+    fprintf(stderr, "%-20s Load the default \"builtins\" root CA module\n", "-b");
+    fprintf(stderr, "%-20s Load the given root CA module\n", "-R");
+    fprintf(stderr, "%-20s Print certificate chain information\n", "-C");
+    fprintf(stderr, "%-20s (use -C twice to print more certificate details)\n", "");
+    fprintf(stderr, "%-20s (use -C three times to include PEM format certificate dumps)\n", "");
     fprintf(stderr, "%-20s Nickname of key and cert for client auth\n", 
                     "-n nickname");
     fprintf(stderr, 
             "%-20s Bypass PKCS11 layer for SSL encryption and MACing.\n", "-B");
     fprintf(stderr, 
             "%-20s Restricts the set of enabled SSL/TLS protocols versions.\n"
             "%-20s All versions are enabled by default.\n"
             "%-20s Possible values for min/max: ssl2 ssl3 tls1.0 tls1.1 tls1.2\n"
@@ -495,22 +504,124 @@ verifyFromSideChannel(CERTCertificate *c
             EXIT_CODE_SIDECHANNELTEST_NODATA;
         return;
     }
     
     sca->sideChannelRevocationTestResultCode = 
         EXIT_CODE_SIDECHANNELTEST_REVOKED;
 }
 
+
+static void
+dumpCertificatePEM(CERTCertificate *cert)
+{
+    SECItem data;
+    data.data = cert->derCert.data;
+    data.len = cert->derCert.len;
+    fprintf(stderr, "%s\n%s\n%s\n", NS_CERT_HEADER, 
+	    BTOA_DataToAscii(data.data, data.len), NS_CERT_TRAILER);
+}
+
+static void
+dumpServerCertificateChain(PRFileDesc *fd)
+{
+    CERTCertList *peerCertChain = NULL;
+    CERTCertListNode *node = NULL;
+    CERTCertificate *peerCert = NULL;
+    CERTCertificateList *foundChain = NULL;
+    SECU_PPFunc dumpFunction = NULL;
+    PRBool dumpCertPEM = PR_FALSE;
+
+    if (!dumpServerChain) {
+	return;
+    }
+    else if (dumpServerChain == 1) {
+	dumpFunction = SECU_PrintCertificateBasicInfo;
+    } else {
+	dumpFunction = SECU_PrintCertificate;
+	if (dumpServerChain > 2) {
+	    dumpCertPEM = PR_TRUE;
+	}
+    }
+
+    SECU_EnableWrap(PR_FALSE);
+
+    fprintf(stderr, "==== certificate(s) sent by server: ====\n");
+    peerCertChain = SSL_PeerCertificateChain(fd);
+    if (peerCertChain) {
+        node = CERT_LIST_HEAD(peerCertChain);
+        while ( ! CERT_LIST_END(node, peerCertChain) ) {
+            CERTCertificate *cert = node->cert;
+            SECU_PrintSignedContent(stderr, &cert->derCert, "Certificate", 0,
+                                    dumpFunction);
+	    if (dumpCertPEM) {
+		dumpCertificatePEM(cert);
+	    }
+            node = CERT_LIST_NEXT(node);   
+        }
+    }
+
+    if (peerCertChain) {
+	peerCert = SSL_RevealCert(fd);
+	if (peerCert) {
+	    foundChain = CERT_CertChainFromCert(peerCert, certificateUsageSSLServer,
+						PR_TRUE);
+	}
+	if (foundChain) {
+	    int count = 0;
+	    fprintf(stderr, "==== locally found issuer certificate(s): ====\n");
+	    for(count = 0; count < (unsigned int)foundChain->len; count++) {
+		CERTCertificate *c;
+		PRBool wasSentByServer = PR_FALSE;
+		c = CERT_FindCertByDERCert(CERT_GetDefaultCertDB(), &foundChain->certs[count]);
+
+		node = CERT_LIST_HEAD(peerCertChain);
+		while ( ! CERT_LIST_END(node, peerCertChain) ) {
+		    CERTCertificate *cert = node->cert;
+		    if (CERT_CompareCerts(cert, c)) {
+			wasSentByServer = PR_TRUE;
+			break;
+		    }
+		    node = CERT_LIST_NEXT(node);   
+		}
+		
+		if (!wasSentByServer) {
+		    SECU_PrintSignedContent(stderr, &c->derCert, "Certificate", 0,
+					    dumpFunction);
+		    if (dumpCertPEM) {
+			dumpCertificatePEM(c);
+		    }
+		}
+		CERT_DestroyCertificate(c);
+	    }
+	    CERT_DestroyCertificateList(foundChain);
+	}
+	if (peerCert) {
+	    CERT_DestroyCertificate(peerCert);
+	}
+
+	CERT_DestroyCertList(peerCertChain);
+	peerCertChain = NULL;
+    }
+
+    fprintf(stderr, "==== end of certificate chain information ====\n");
+    fflush(stderr);
+}
+
 static SECStatus 
 ownAuthCertificate(void *arg, PRFileDesc *fd, PRBool checkSig,
                        PRBool isServer)
 {
     ServerCertAuth * serverCertAuth = (ServerCertAuth *) arg;
 
+    if (dumpServerChain) {
+	dumpServerCertificateChain(fd);
+    }
+
+
     if (!serverCertAuth->shouldPause) {
         CERTCertificate *cert;
         int i;
         const SECItemArray *csa;
 
         if (!serverCertAuth->testFreshStatusFromSideChannel) {
             return SSL_AuthCertificate(serverCertAuth->dbHandle, 
                                        fd, checkSig, isServer);
@@ -823,16 +934,19 @@ int main(int argc, char **argv)
     int                headerSeparatorPtrnId = 0;
     int                error = 0;
     PRUint16           portno = 443;
     char *             hs1SniHostName = NULL;
     char *             hs2SniHostName = NULL;
     PLOptState *optstate;
     PLOptStatus optstatus;
     PRStatus prStatus;
+    PRBool openDB = PR_TRUE;
+    PRBool loadDefaultRootCAs = PR_FALSE;
+    char *rootModule = NULL;
 
     serverCertAuth.shouldPause = PR_TRUE;
     serverCertAuth.isPaused = PR_FALSE;
     serverCertAuth.dbHandle = NULL;
     serverCertAuth.testFreshStatusFromSideChannel = PR_FALSE;
     serverCertAuth.sideChannelRevocationTestResultCode = EXIT_CODE_HANDSHAKE_FAILED;
     serverCertAuth.requireDataForIntermediates = PR_FALSE;
     serverCertAuth.allowOCSPSideChannelData = PR_TRUE;
@@ -849,27 +963,31 @@ int main(int argc, char **argv)
        if (sec > 0) {
            maxInterval = PR_SecondsToInterval(sec);
        }
     }
 
     SSL_VersionRangeGetSupported(ssl_variant_stream, &enabledVersions);
 
     optstate = PL_CreateOptState(argc, argv,
-                                 "46BFKM:OSTV:W:Ya:c:d:fgh:m:n:op:qr:st:uvw:xz");
+                                 "46BCDFKM:OR:STV:W:Ya:bc:d:fgh:m:n:op:qr:st:uvw:xz");
     while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
 	switch (optstate->option) {
 	  case '?':
 	  default : Usage(progName); 			break;
 
           case '4': allowIPv6 = PR_FALSE; if (!allowIPv4) Usage(progName); break;
           case '6': allowIPv4 = PR_FALSE; if (!allowIPv6) Usage(progName); break;
 
           case 'B': bypassPKCS11 = 1; 			break;
 
+          case 'C': ++dumpServerChain; 			break;
+
+          case 'D': openDB = PR_FALSE; 			break;
+
           case 'F': if (serverCertAuth.testFreshStatusFromSideChannel) {
                         /* parameter given twice or more */
                         serverCertAuth.requireDataForIntermediates = PR_TRUE;
                     }
                     serverCertAuth.testFreshStatusFromSideChannel = PR_TRUE;
                     break;
 
 	  case 'I': /* reserved for OCSP multi-stapling */ break;
@@ -890,16 +1008,18 @@ int main(int argc, char **argv)
                       case 0:
                       default:
                           serverCertAuth.allowOCSPSideChannelData = PR_TRUE;
                           serverCertAuth.allowCRLSideChannelData = PR_TRUE;
                           break;
                     };
                     break;
 
+          case 'R': rootModule = PORT_Strdup(optstate->value); break;
+
           case 'S': skipProtoHeader = PR_TRUE;                 break;
 
           case 'T': enableCertStatus = 1;               break;
 
           case 'V': if (SECU_ParseSSLVersionRangeString(optstate->value,
                             enabledVersions, enableSSL2,
                             &enabledVersions, &enableSSL2) != SECSuccess) {
                         Usage(progName);
@@ -912,16 +1032,18 @@ int main(int argc, char **argv)
                         hs1SniHostName = PORT_Strdup(optstate->value);
                     } else if (!hs2SniHostName) {
                         hs2SniHostName =  PORT_Strdup(optstate->value);
                     } else {
                         Usage(progName);
                     }
                     break;
 
+          case 'b': loadDefaultRootCAs = PR_TRUE;                 break;
+
           case 'c': cipherString = PORT_Strdup(optstate->value); break;
 
           case 'g': enableFalseStart = 1; 		break;
 
           case 'd': certDir = PORT_Strdup(optstate->value);   break;
 
           case 'f': clientSpeaksFirst = PR_TRUE;        break;
 
@@ -967,25 +1089,37 @@ int main(int argc, char **argv)
 	}
     }
 
     PL_DestroyOptState(optstate);
 
     if (optstatus == PL_OPT_BAD)
 	Usage(progName);
 
-    if (!host || !portno) 
+    if (!host || !portno) {
+        fprintf(stderr, "%s: parameters -h and -p are mandatory\n", progName);
     	Usage(progName);
+    }
 
     if (serverCertAuth.testFreshStatusFromSideChannel
         && serverCertAuth.shouldPause) {
         fprintf(stderr, "%s: -F requires the use of -O\n", progName);
         exit(1);
     }
 
+    if (certDir && !openDB) {
+        fprintf(stderr, "%s: Cannot combine parameters -D and -d\n", progName);
+        exit(1);
+    }
+
+    if (rootModule && loadDefaultRootCAs) {
+        fprintf(stderr, "%s: Cannot combine parameters -b and -R\n", progName);
+        exit(1);
+    }
+
     PR_Init( PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
 
     PK11_SetPasswordFunc(SECU_GetModulePassword);
 
     status = PR_StringToNetAddr(host, &addr);
     if (status == PR_SUCCESS) {
     	addr.inet.port = PR_htons(portno);
     } else {
@@ -1068,20 +1202,36 @@ int main(int argc, char **argv)
     if (!certDir) {
         certDir = SECU_DefaultSSLDir(); /* Look in $SSL_DIR */
         certDir = SECU_ConfigDirectory(certDir);
     } else {
         char *certDirTmp = certDir;
         certDir = SECU_ConfigDirectory(certDirTmp);
         PORT_Free(certDirTmp);
     }
-    rv = NSS_Init(certDir);
-    if (rv != SECSuccess) {
-        SECU_PrintError(progName, "unable to open cert database");
-        return 1;
+
+    if (openDB) {
+	rv = NSS_Init(certDir);
+	if (rv != SECSuccess) {
+	    SECU_PrintError(progName, "unable to open cert database");
+	    return 1;
+	}
+    } else {
+	rv = NSS_NoDB_Init(NULL);
+	if (rv != SECSuccess) {
+	    SECU_PrintError(progName, "failed to initialize NSS");
+	    return 1;
+	}
+    }
+
+    if (loadDefaultRootCAs) {
+	SECMOD_AddNewModule("Builtins",
+			    DLL_PREFIX"nssckbi."DLL_SUFFIX, 0, 0);
+    } else if (rootModule) {
+	SECMOD_AddNewModule("Builtins", rootModule, 0, 0);
     }
 
     /* set the policy bits true for all the cipher suites. */
     if (useExportPolicy)
         NSS_SetExportPolicy();
     else
         NSS_SetDomesticPolicy();