SSL certificate chain validation issues

Mark Doliner mark at kingant.net
Sun Jun 22 20:16:03 EDT 2014


Attached patch that adds basic constraint checking for both gnutls and
nss. I think we should go with this in 2.x.y, and save more elaborate
fixes for the default branch.

Anyone want to review this and make sure it looks sane? I think the
gnutls code is basically the same as what I sent in my previous email.
The libnss checks were inspired by the cert_VerifyCertChainOld()
function. I'm hoping that this code is all temporary, and in we'll
switch to using higher-level cert verification functions in default.
-------------- next part --------------
diff -r c4ebf4d738f9 libpurple/certificate.h
--- a/libpurple/certificate.h	Tue Jun 17 23:14:13 2014 -0700
+++ b/libpurple/certificate.h	Sun Jun 22 17:08:16 2014 -0700
@@ -197,7 +197,8 @@ struct _PurpleCertificateScheme
 	 */
 	void (* destroy_certificate)(PurpleCertificate * crt);
 
-	/** Find whether "crt" has a valid signature from issuer "issuer"
+	/** Find whether "crt" has a valid signature from "issuer" and issuer set the
+	 * CA flag to true in the basic constraints extension.
 	 *  @see purple_certificate_signed_by() */
 	gboolean (*signed_by)(PurpleCertificate *crt, PurpleCertificate *issuer);
 	/**
diff -r c4ebf4d738f9 libpurple/plugins/ssl/ssl-gnutls.c
--- a/libpurple/plugins/ssl/ssl-gnutls.c	Tue Jun 17 23:14:13 2014 -0700
+++ b/libpurple/plugins/ssl/ssl-gnutls.c	Sun Jun 22 17:08:16 2014 -0700
@@ -872,7 +872,7 @@ x509_certificate_signed_by(PurpleCertifi
 	crt_dat = X509_GET_GNUTLS_DATA(crt);
 	issuer_dat = X509_GET_GNUTLS_DATA(issuer);
 
-	/* First, let's check that crt.issuer is actually issuer */
+	/* Ensure crt issuer matches the name on the issuer cert. */
 	ret = gnutls_x509_crt_check_issuer(crt_dat, issuer_dat);
 	if (ret <= 0) {
 
@@ -901,6 +901,26 @@ x509_certificate_signed_by(PurpleCertifi
 		return FALSE;
 	}
 
+	/* Ensure issuer has CA flag set to true in the basic constraints
+	   extension. */
+	ret = gnutls_x509_crt_get_basic_constraints(issuer_dat, NULL, NULL, NULL);
+	if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+		/* Issuer certificate is missing the basic constraints extension.
+		   Allow it to act as a CA. */
+	} else if (ret <= 0) {
+		/* Issuer certificate has basic constraints extension and the CA flag
+		   is set to false. Reject the certificate. */
+		gchar *issuer_id;
+
+		issuer_id = purple_certificate_get_unique_id(issuer);
+		purple_debug_info("gnutls/x509", "CA flag is set to false in the "
+				"basic constraints extension for issuer cert %s. ",
+				"ret=%d\n", issuer_id ? issuer_id : "(null)", ret);
+		g_free(issuer_id);
+
+		return FALSE;
+	}
+
 	/* Now, check the signature */
 	/* The second argument is a ptr to an array of "trusted" issuer certs,
 	   but we're only using one trusted one */
diff -r c4ebf4d738f9 libpurple/plugins/ssl/ssl-nss.c
--- a/libpurple/plugins/ssl/ssl-nss.c	Tue Jun 17 23:14:13 2014 -0700
+++ b/libpurple/plugins/ssl/ssl-nss.c	Sun Jun 22 17:08:16 2014 -0700
@@ -734,6 +734,44 @@ x509_destroy_certificate(PurpleCertifica
 	g_free(crt);
 }
 
+/**
+ * This function was copied from
+ * https://mxr.mozilla.org/security/source/security/nss/lib/certdb/certv3.c
+ * and is licensed under the terms of the Mozilla Public License, v. 2.0.
+ *
+ * This function only exists here because the version in libnss doesn't seem
+ * to be exported even though it's listed in nss/cert.h.
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=621045
+ *
+ * In the future we should get rid of this function and instead switch to
+ * using higher level verification functions, such as CERT_VerifyCertNow()
+ * or CERT_VerifyCert().
+ */
+static SECStatus
+my_CERT_FindBasicConstraintExten(CERTCertificate *cert,
+		CERTBasicConstraints *value)
+{
+	SECItem encodedExtenValue;
+	SECStatus rv;
+
+	encodedExtenValue.data = NULL;
+	encodedExtenValue.len = 0;
+
+	rv = CERT_FindCertExtension(cert, SEC_OID_X509_BASIC_CONSTRAINTS,
+			&encodedExtenValue);
+	if (rv != SECSuccess) {
+		return rv;
+	}
+
+	rv = CERT_DecodeBasicConstraintValue(value, &encodedExtenValue);
+
+	/* free the raw extension data */
+	PORT_Free(encodedExtenValue.data);
+	encodedExtenValue.data = NULL;
+
+	return rv;
+}
+
 /** Determines whether one certificate has been issued and signed by another
  *
  * @param crt       Certificate to check the signature of
@@ -748,6 +786,7 @@ x509_signed_by(PurpleCertificate * crt,
 {
 	CERTCertificate *subjectCert;
 	CERTCertificate *issuerCert;
+	CERTBasicConstraints basicConstraint;
 	SECStatus st;
 
 	issuerCert = X509_NSS_DATA(issuer);
@@ -756,9 +795,63 @@ x509_signed_by(PurpleCertificate * crt,
 	subjectCert = X509_NSS_DATA(crt);
 	g_return_val_if_fail(subjectCert, FALSE);
 
+	/* Ensure that crt issuer matches the name on the issuer cert. */
+	/* TODO: Could/should use CERT_VerifyCertName() here? Even better would be
+	   to stop doing all this crap ourselves and use CERT_VerifyCertNow() or
+	   CERT_VerifyCert() directly. */
 	if (subjectCert->issuerName == NULL || issuerCert->subjectName == NULL
-			|| PORT_Strcmp(subjectCert->issuerName, issuerCert->subjectName) != 0)
+			|| PORT_Strcmp(subjectCert->issuerName, issuerCert->subjectName) != 0) {
+		purple_debug_info("nss/x509", "Certificate %s is issued by %s, which "
+				"does not match %s.\n",
+				subjectCert->subjectName ? subjectCert->subjectName : "(null)",
+				subjectCert->issuerName ? subjectCert->issuerName : "(null)",
+				issuerCert->subjectName ? issuerCert->subjectName : "(null)");
 		return FALSE;
+	}
+
+	/* Ensure issuer has CA flag set to true in the basic constraints
+	   extension. */
+	st = my_CERT_FindBasicConstraintExten(issuerCert, &basicConstraint);
+	if (st == SECSuccess) {
+		/* Successfully read basic constraints extension from issuer cert. */
+		if (basicConstraint.isCA == PR_FALSE) {
+			/* CA flag is set to false! */
+			purple_debug_error("nss/x509", "CA flag is set to false in the "
+					"basic constraints extension for issuer cert %s.\n",
+					issuerCert->subjectName ? issuerCert->subjectName : "(null)");
+			return FALSE;
+		}
+		/* CA flag is set to true. Great. */
+	} else {
+		/* Error reading basic constraints extension from issuer cert. */
+		if (PORT_GetError() != SEC_ERROR_EXTENSION_NOT_FOUND) {
+			/* Unknown error. */
+			purple_debug_error("nss/x509", "Error attempting to read basic "
+					"constraints from issuer cert %s. ret=%d\n",
+					issuerCert->subjectName ? issuerCert->subjectName : "(null)",
+					PORT_GetError());
+			return FALSE;
+		}
+		/* Basic constraints extension is absent. Allow issuer to act as a CA. */
+	}
+
+	/* Make sure issuer cert is an SSL CA. */
+	if (!(issuerCert->nsCertType & NS_CERT_TYPE_SSL_CA)) {
+		purple_debug_error("nss/x509", "Issuer cert %s is not an SSL CA.\n",
+				issuerCert->subjectName ? issuerCert->subjectName : "(null)");
+		return FALSE;
+	}
+
+	/* Make sure key usage allows cert signing. Would ideally call
+	   CERT_CheckKeyUsage here, but that function seems to not be exported
+	   even though it's listed in nss/cert.h. */
+	if (!(issuerCert->keyUsage & KU_KEY_CERT_SIGN)) {
+		purple_debug_error("nss/x509",
+				"Issuer cert %s does not allow signing.\n",
+				issuerCert->subjectName ? issuerCert->subjectName : "(null)");
+		return FALSE;
+	}
+
 	st = CERT_VerifySignedData(&subjectCert->signatureWrap, issuerCert, PR_Now(), NULL);
 	return st == SECSuccess;
 }


More information about the security mailing list