summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergei Chernov <sergei.cv@ndivi.com>2015-12-08 17:11:24 -0500
committerSergei Chernov <sergei.cv@ndivi.com>2015-12-08 17:11:24 -0500
commitd38cf7ddfbc3f615f2af916dba06c8e87f2b0c61 (patch)
tree675ef6045d3ba6b138103c529359fb46f63f8d98
parent4aab1ad9f2863caf05e8358a9bbb317abd72116f (diff)
downloadnss-hg-d38cf7ddfbc3f615f2af916dba06c8e87f2b0c61.tar.gz
Bug 944175 - Implement Certificate Transparency [part 1, client side]. r=wtc
-rw-r--r--cmd/tstclnt/tstclnt.c22
-rw-r--r--lib/ssl/ssl.def1
-rw-r--r--lib/ssl/ssl.h19
-rw-r--r--lib/ssl/ssl3con.c14
-rw-r--r--lib/ssl/ssl3ext.c72
-rw-r--r--lib/ssl/sslimpl.h18
-rw-r--r--lib/ssl/sslnonce.c3
-rw-r--r--lib/ssl/sslsock.c40
-rw-r--r--lib/ssl/sslt.h4
-rwxr-xr-xtests/ssl/ssl.sh42
10 files changed, 230 insertions, 5 deletions
diff --git a/cmd/tstclnt/tstclnt.c b/cmd/tstclnt/tstclnt.c
index 13ae53a1e..4f4c4d9c4 100644
--- a/cmd/tstclnt/tstclnt.c
+++ b/cmd/tstclnt/tstclnt.c
@@ -110,6 +110,7 @@ void printSecurityInfo(PRFileDesc *fd)
{
CERTCertificate * cert;
const SECItemArray *csa;
+ const SECItem *scts;
SSL3Statistics * ssl3stats = SSL_GetStatistics();
SECStatus result;
SSLChannelInfo channel;
@@ -162,6 +163,11 @@ void printSecurityInfo(PRFileDesc *fd)
fprintf(stderr, "Received %d Cert Status items (OCSP stapled data)\n",
csa->len);
}
+ scts = SSL_PeerSignedCertTimestamps(fd);
+ if (scts && scts->len) {
+ fprintf(stderr, "Received a Signed Certificate Timestamp of length"
+ " %u\n", scts->len);
+ }
}
void
@@ -184,7 +190,7 @@ static void PrintUsageHeader(const char *progName)
"Usage: %s -h host [-a 1st_hs_name ] [-a 2nd_hs_name ] [-p port]\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"
+ "[-V [min-version]:[max-version]] [-K] [-T] [-U]\n"
"[-r N] [-w passwd] [-W pwfile] [-q [-t seconds]]\n",
progName);
}
@@ -232,6 +238,7 @@ static void PrintParameterUsage(void)
fprintf(stderr, "%-20s Enable compression.\n", "-z");
fprintf(stderr, "%-20s Enable false start.\n", "-g");
fprintf(stderr, "%-20s Enable the cert_status extension (OCSP stapling).\n", "-T");
+ fprintf(stderr, "%-20s Enable the signed_certificate_timestamp extension.\n", "-U");
fprintf(stderr, "%-20s Enable the extended master secret extension (session hash).\n", "-G");
fprintf(stderr, "%-20s Require fresh revocation info from side channel.\n"
"%-20s -F once means: require for server cert only\n"
@@ -921,6 +928,7 @@ int main(int argc, char **argv)
int enableCompression = 0;
int enableFalseStart = 0;
int enableCertStatus = 0;
+ int enableSignedCertTimestamps = 0;
int forceFallbackSCSV = 0;
int enableExtendedMasterSecret = 0;
PRSocketOptionData opt;
@@ -971,7 +979,7 @@ int main(int argc, char **argv)
SSL_VersionRangeGetSupported(ssl_variant_stream, &enabledVersions);
optstate = PL_CreateOptState(argc, argv,
- "46BCDFGKM:OR:STV:W:Ya:bc:d:fgh:m:n:op:qr:st:uvw:xz");
+ "46BCDFGKM:OR:STUV:W:Ya:bc:d:fgh:m:n:op:qr:st:uvw:xz");
while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
switch (optstate->option) {
case '?':
@@ -1024,6 +1032,8 @@ int main(int argc, char **argv)
case 'T': enableCertStatus = 1; break;
+ case 'U': enableSignedCertTimestamps = 1; break;
+
case 'V': if (SECU_ParseSSLVersionRangeString(optstate->value,
enabledVersions, enableSSL2,
&enabledVersions, &enableSSL2) != SECSuccess) {
@@ -1401,6 +1411,14 @@ int main(int argc, char **argv)
}
}
+ /* enable Signed Certificate Timestamps. */
+ rv = SSL_OptionSet(s, SSL_ENABLE_SIGNED_CERT_TIMESTAMPS,
+ enableSignedCertTimestamps);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "error enabling signed cert timestamps");
+ return 1;
+ }
+
SSL_SetPKCS11PinArg(s, &pwdata);
serverCertAuth.dbHandle = CERT_GetDefaultCertDB();
diff --git a/lib/ssl/ssl.def b/lib/ssl/ssl.def
index 44db4e5ee..dfeb41bab 100644
--- a/lib/ssl/ssl.def
+++ b/lib/ssl/ssl.def
@@ -181,6 +181,7 @@ SSL_EnableWeakDHEPrimeGroup;
;+NSS_3.21 { # NSS 3.21 release
;+ global:
SSL_GetPreliminaryChannelInfo;
+SSL_PeerSignedCertTimestamps;
SSL_SignaturePrefSet;
SSL_SignaturePrefGet;
SSL_SignatureMaxCount;
diff --git a/lib/ssl/ssl.h b/lib/ssl/ssl.h
index 2a527693b..eb869f1d2 100644
--- a/lib/ssl/ssl.h
+++ b/lib/ssl/ssl.h
@@ -203,6 +203,8 @@ SSL_IMPORT PRFileDesc *DTLS_ImportFD(PRFileDesc *model, PRFileDesc *fd);
*/
#define SSL_ENABLE_EXTENDED_MASTER_SECRET 30
+/* Request Signed Certificate Timestamps via TLS extension (client) */
+#define SSL_ENABLE_SIGNED_CERT_TIMESTAMPS 31
#ifdef SSL_DEPRECATED_FUNCTION
/* Old deprecated function names */
@@ -560,6 +562,23 @@ SSL_IMPORT CERTCertList *SSL_PeerCertificateChain(PRFileDesc *fd);
*/
SSL_IMPORT const SECItemArray * SSL_PeerStapledOCSPResponses(PRFileDesc *fd);
+/* SSL_PeerSignedCertTimestamps returns the signed_certificate_timestamp
+ * extension data provided by the TLS server. The return value is a pointer
+ * to an internal SECItem that contains the returned response (as a serialized
+ * SignedCertificateTimestampList, see RFC 6962). The returned pointer is only
+ * valid until the callback function that calls SSL_PeerSignedCertTimestamps
+ * (e.g. the authenticate certificate hook, or the handshake callback) returns.
+ *
+ * If no Signed Certificate Timestamps were given by the server then the result
+ * will be empty. If there was an error, then the result will be NULL.
+ *
+ * You must set the SSL_ENABLE_SIGNED_CERT_TIMESTAMPS option to indicate support
+ * for Signed Certificate Timestamps to a server.
+ *
+ * libssl does not do any parsing or validation of the response itself.
+ */
+SSL_IMPORT const SECItem * SSL_PeerSignedCertTimestamps(PRFileDesc *fd);
+
/* SSL_SetStapledOCSPResponses stores an array of one or multiple OCSP responses
* in the fd's data, which may be sent as part of a server side cert_status
* handshake message. Parameter |responses| is for the server certificate of
diff --git a/lib/ssl/ssl3con.c b/lib/ssl/ssl3con.c
index 67f58d403..93e7d53ed 100644
--- a/lib/ssl/ssl3con.c
+++ b/lib/ssl/ssl3con.c
@@ -6787,6 +6787,17 @@ ssl3_HandleServerHello(sslSocket *ss, SSL3Opaque *b, PRUint32 length)
sid->u.ssl3.keys.extendedMasterSecretUsed =
ssl3_ExtensionNegotiated(ss, ssl_extended_master_secret_xtn);
+ /* Copy Signed Certificate Timestamps, if any. */
+ if (ss->xtnData.signedCertTimestamps.data) {
+ rv = SECITEM_CopyItem(NULL, &sid->u.ssl3.signedCertTimestamps,
+ &ss->xtnData.signedCertTimestamps);
+ if (rv != SECSuccess)
+ goto loser;
+ /* Clean up the temporary pointer to the handshake buffer. */
+ ss->xtnData.signedCertTimestamps.data = NULL;
+ ss->xtnData.signedCertTimestamps.len = 0;
+ }
+
ss->ssl3.hs.isResuming = PR_FALSE;
if (ss->ssl3.hs.kea_def->signKeyType != sign_null) {
/* All current cipher suites other than those with sign_null (i.e.,
@@ -6805,6 +6816,9 @@ alert_loser:
(void)SSL3_SendAlert(ss, alert_fatal, desc);
loser:
+ /* Clean up the temporary pointer to the handshake buffer. */
+ ss->xtnData.signedCertTimestamps.data = NULL;
+ ss->xtnData.signedCertTimestamps.len = 0;
ssl_MapLowLevelError(errCode);
return SECFailure;
}
diff --git a/lib/ssl/ssl3ext.c b/lib/ssl/ssl3ext.c
index 7931618e0..c7306454f 100644
--- a/lib/ssl/ssl3ext.c
+++ b/lib/ssl/ssl3ext.c
@@ -87,6 +87,13 @@ static PRInt32 ssl3_ClientSendSigAlgsXtn(sslSocket *ss, PRBool append,
static SECStatus ssl3_ServerHandleSigAlgsXtn(sslSocket *ss, PRUint16 ex_type,
SECItem *data);
+static PRInt32 ssl3_ClientSendSignedCertTimestampXtn(sslSocket *ss,
+ PRBool append,
+ PRUint32 maxBytes);
+static SECStatus ssl3_ClientHandleSignedCertTimestampXtn(sslSocket *ss,
+ PRUint16 ex_type,
+ SECItem *data);
+
static PRInt32 ssl3_ClientSendDraftVersionXtn(sslSocket *ss, PRBool append,
PRUint32 maxBytes);
static SECStatus ssl3_ServerHandleDraftVersionXtn(sslSocket *ss, PRUint16 ex_type,
@@ -278,6 +285,7 @@ static const ssl3HelloExtensionHandler serverHelloHandlersTLS[] = {
{ ssl_use_srtp_xtn, &ssl3_ClientHandleUseSRTPXtn },
{ ssl_cert_status_xtn, &ssl3_ClientHandleStatusRequestXtn },
{ ssl_extended_master_secret_xtn, &ssl3_HandleExtendedMasterSecretXtn },
+ { ssl_signed_cert_timestamp_xtn, &ssl3_ClientHandleSignedCertTimestampXtn },
{ -1, NULL }
};
@@ -308,6 +316,7 @@ ssl3HelloExtensionSender clientHelloSendersTLS[SSL_MAX_EXTENSIONS] = {
{ ssl_signature_algorithms_xtn, &ssl3_ClientSendSigAlgsXtn },
{ ssl_tls13_draft_version_xtn, &ssl3_ClientSendDraftVersionXtn },
{ ssl_extended_master_secret_xtn, &ssl3_SendExtendedMasterSecretXtn},
+ { ssl_signed_cert_timestamp_xtn, &ssl3_ClientSendSignedCertTimestampXtn },
/* any extra entries will appear as { 0, NULL } */
};
@@ -2688,3 +2697,66 @@ ssl3_HandleExtendedMasterSecretXtn(sslSocket * ss, PRUint16 ex_type,
}
return SECSuccess;
}
+
+
+/* ssl3_ClientSendSignedCertTimestampXtn sends the signed_certificate_timestamp
+ * extension for TLS ClientHellos. */
+static PRInt32
+ssl3_ClientSendSignedCertTimestampXtn(sslSocket *ss, PRBool append,
+ PRUint32 maxBytes)
+{
+ PRInt32 extension_length = 2 /* extension_type */ +
+ 2 /* length(extension_data) */;
+
+ /* Only send the extension if processing is enabled. */
+ if (!ss->opt.enableSignedCertTimestamps)
+ return 0;
+
+ if (append && maxBytes >= extension_length) {
+ SECStatus rv;
+ /* extension_type */
+ rv = ssl3_AppendHandshakeNumber(ss,
+ ssl_signed_cert_timestamp_xtn,
+ 2);
+ if (rv != SECSuccess)
+ goto loser;
+ /* zero length */
+ rv = ssl3_AppendHandshakeNumber(ss, 0, 2);
+ if (rv != SECSuccess)
+ goto loser;
+ ss->xtnData.advertised[ss->xtnData.numAdvertised++] =
+ ssl_signed_cert_timestamp_xtn;
+ } else if (maxBytes < extension_length) {
+ PORT_Assert(0);
+ return 0;
+ }
+
+ return extension_length;
+loser:
+ return -1;
+}
+
+static SECStatus
+ssl3_ClientHandleSignedCertTimestampXtn(sslSocket *ss, PRUint16 ex_type,
+ SECItem *data)
+{
+ /* We do not yet know whether we'll be resuming a session or creating
+ * a new one, so we keep a pointer to the data in the TLSExtensionData
+ * structure. This pointer is only valid in the scope of
+ * ssl3_HandleServerHello, and, if not resuming a session, the data is
+ * copied once a new session structure has been set up.
+ * All parsing is currently left to the application and we accept
+ * everything, including empty data.
+ */
+ SECItem *scts = &ss->xtnData.signedCertTimestamps;
+ PORT_Assert(!scts->data && !scts->len);
+
+ if (!data->len) {
+ /* Empty extension data: RFC 6962 mandates non-empty contents. */
+ return SECFailure;
+ }
+ *scts = *data;
+ /* Keep track of negotiated extensions. */
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ex_type;
+ return SECSuccess;
+}
diff --git a/lib/ssl/sslimpl.h b/lib/ssl/sslimpl.h
index 6dad6a7e9..812c2b297 100644
--- a/lib/ssl/sslimpl.h
+++ b/lib/ssl/sslimpl.h
@@ -338,6 +338,7 @@ typedef struct sslOptionsStr {
unsigned int enableFallbackSCSV : 1; /* 29 */
unsigned int enableServerDhe : 1; /* 30 */
unsigned int enableExtendedMS : 1; /* 31 */
+ unsigned int enableSignedCertTimestamps : 1; /* 32 */
} sslOptions;
typedef enum { sslHandshakingUndetermined = 0,
@@ -701,6 +702,11 @@ struct sslSessionIDStr {
SECItem srvName;
+ /* Signed certificate timestamps received in a TLS extension.
+ ** (used only in client).
+ */
+ SECItem signedCertTimestamps;
+
/* This lock is lazily initialized by CacheSID when a sid is first
* cached. Before then, there is no need to lock anything because
* the sid isn't being shared by anything.
@@ -815,6 +821,18 @@ struct TLSExtensionDataStr {
* is beyond ssl3_HandleClientHello function. */
SECItem *sniNameArr;
PRUint32 sniNameArrSize;
+
+ /* Signed Certificate Timestamps extracted from the TLS extension.
+ * (client only).
+ * This container holds a temporary pointer to the extension data,
+ * until a session structure (the sec.ci.sid of an sslSocket) is setup
+ * that can hold a permanent copy of the data
+ * (in sec.ci.sid.u.ssl3.signedCertTimestamps).
+ * The data pointed to by this structure is neither explicitly allocated
+ * nor copied: the pointer points to the handshake message buffer and is
+ * only valid in the scope of ssl3_HandleServerHello.
+ */
+ SECItem signedCertTimestamps;
};
typedef SECStatus (*sslRestartTarget)(sslSocket *);
diff --git a/lib/ssl/sslnonce.c b/lib/ssl/sslnonce.c
index 2e861f157..bb4da3b90 100644
--- a/lib/ssl/sslnonce.c
+++ b/lib/ssl/sslnonce.c
@@ -179,6 +179,9 @@ ssl_DestroySID(sslSessionID *sid)
if (sid->u.ssl3.srvName.data) {
SECITEM_FreeItem(&sid->u.ssl3.srvName, PR_FALSE);
}
+ if (sid->u.ssl3.signedCertTimestamps.data) {
+ SECITEM_FreeItem(&sid->u.ssl3.signedCertTimestamps, PR_FALSE);
+ }
if (sid->u.ssl3.lock) {
PR_DestroyRWLock(sid->u.ssl3.lock);
diff --git a/lib/ssl/sslsock.c b/lib/ssl/sslsock.c
index f73500925..bdb09246f 100644
--- a/lib/ssl/sslsock.c
+++ b/lib/ssl/sslsock.c
@@ -85,7 +85,8 @@ static sslOptions ssl_defaults = {
PR_TRUE, /* reuseServerECDHEKey */
PR_FALSE, /* enableFallbackSCSV */
PR_TRUE, /* enableServerDhe */
- PR_FALSE /* enableExtendedMS */
+ PR_FALSE, /* enableExtendedMS */
+ PR_FALSE, /* enableSignedCertTimestamps */
};
/*
@@ -830,6 +831,10 @@ SSL_OptionSet(PRFileDesc *fd, PRInt32 which, PRBool on)
ss->opt.enableExtendedMS = on;
break;
+ case SSL_ENABLE_SIGNED_CERT_TIMESTAMPS:
+ ss->opt.enableSignedCertTimestamps = on;
+ break;
+
default:
PORT_SetError(SEC_ERROR_INVALID_ARGS);
rv = SECFailure;
@@ -908,6 +913,9 @@ SSL_OptionGet(PRFileDesc *fd, PRInt32 which, PRBool *pOn)
case SSL_ENABLE_SERVER_DHE: on = ss->opt.enableServerDhe; break;
case SSL_ENABLE_EXTENDED_MASTER_SECRET:
on = ss->opt.enableExtendedMS; break;
+ case SSL_ENABLE_SIGNED_CERT_TIMESTAMPS:
+ on = ss->opt.enableSignedCertTimestamps;
+ break;
default:
PORT_SetError(SEC_ERROR_INVALID_ARGS);
@@ -983,6 +991,9 @@ SSL_OptionGetDefault(PRInt32 which, PRBool *pOn)
case SSL_ENABLE_EXTENDED_MASTER_SECRET:
on = ssl_defaults.enableExtendedMS;
break;
+ case SSL_ENABLE_SIGNED_CERT_TIMESTAMPS:
+ on = ssl_defaults.enableSignedCertTimestamps;
+ break;
default:
PORT_SetError(SEC_ERROR_INVALID_ARGS);
@@ -1174,6 +1185,10 @@ SSL_OptionSetDefault(PRInt32 which, PRBool on)
ssl_defaults.enableExtendedMS = on;
break;
+ case SSL_ENABLE_SIGNED_CERT_TIMESTAMPS:
+ ssl_defaults.enableSignedCertTimestamps = on;
+ break;
+
default:
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
@@ -2013,6 +2028,29 @@ ssl3_VersionRangeIsValid(SSLProtocolVariant protocolVariant,
ssl3_VersionIsSupported(protocolVariant, vrange->max);
}
+const SECItem *
+SSL_PeerSignedCertTimestamps(PRFileDesc *fd)
+{
+ sslSocket *ss = ssl_FindSocket(fd);
+
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in SSL_PeerSignedCertTimestamps",
+ SSL_GETPID(), fd));
+ return NULL;
+ }
+
+ if (!ss->sec.ci.sid) {
+ PORT_SetError(SEC_ERROR_NOT_INITIALIZED);
+ return NULL;
+ }
+
+ if (ss->sec.ci.sid->version < SSL_LIBRARY_VERSION_3_0) {
+ PORT_SetError(SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2);
+ return NULL;
+ }
+ return &ss->sec.ci.sid->u.ssl3.signedCertTimestamps;
+}
+
SECStatus
SSL_VersionRangeGetSupported(SSLProtocolVariant protocolVariant,
SSLVersionRange *vrange)
diff --git a/lib/ssl/sslt.h b/lib/ssl/sslt.h
index cd742bbb2..8e893258e 100644
--- a/lib/ssl/sslt.h
+++ b/lib/ssl/sslt.h
@@ -234,6 +234,8 @@ typedef enum {
ssl_signature_algorithms_xtn = 13,
ssl_use_srtp_xtn = 14,
ssl_app_layer_protocol_xtn = 16,
+ /* signed_certificate_timestamp extension, RFC 6962 */
+ ssl_signed_cert_timestamp_xtn = 18,
ssl_padding_xtn = 21,
ssl_extended_master_secret_xtn = 23,
ssl_session_ticket_xtn = 35,
@@ -242,7 +244,7 @@ typedef enum {
ssl_tls13_draft_version_xtn = 0xff02 /* experimental number */
} SSLExtensionType;
-#define SSL_MAX_EXTENSIONS 12 /* doesn't include ssl_padding_xtn. */
+#define SSL_MAX_EXTENSIONS 13 /* doesn't include ssl_padding_xtn. */
typedef enum {
ssl_dhe_group_none = 0,
diff --git a/tests/ssl/ssl.sh b/tests/ssl/ssl.sh
index 1bfb4b74e..ffa826109 100755
--- a/tests/ssl/ssl.sh
+++ b/tests/ssl/ssl.sh
@@ -58,7 +58,7 @@ ssl_init()
PORT=${PORT-8443}
NSS_SSL_TESTS=${NSS_SSL_TESTS:-normal_normal}
- nss_ssl_run="stapling cov auth stress"
+ nss_ssl_run="stapling signed_cert_timestamps cov auth stress"
NSS_SSL_RUN=${NSS_SSL_RUN:-$nss_ssl_run}
# Test case files
@@ -533,6 +533,43 @@ ssl_stapling()
html "</TABLE><BR>"
}
+############################ ssl_signed_cert_timestamps #################
+# local shell function to perform SSL Signed Certificate Timestamp tests
+#########################################################################
+ssl_signed_cert_timestamps()
+{
+ html_head "SSL Signed Certificate Timestamps $NORM_EXT - server $SERVER_MODE/client $CLIENT_MODE $ECC_STRING"
+
+ testname="ssl_signed_cert_timestamps"
+ value=0
+
+ if [ "$SERVER_MODE" = "fips" -o "$CLIENT_MODE" = "fips" ] ; then
+ echo "$SCRIPTNAME: skipping $testname (non-FIPS only)"
+ return 0
+ fi
+
+ echo "${testname}"
+
+ start_selfserv
+
+ # Since we don't have server-side support, this test only covers advertising the
+ # extension in the client hello.
+ echo "tstclnt -p ${PORT} -h ${HOSTADDR} -f -d ${P_R_CLIENTDIR} -v ${CLIENT_OPTIONS} \\"
+ echo " -U -V tls1.0: < ${REQUEST_FILE}"
+ rm ${TMP}/$HOST.tmp.$$ 2>/dev/null
+ ${PROFTOOL} ${BINDIR}/tstclnt -p ${PORT} -h ${HOSTADDR} -f ${CLIENT_OPTIONS} \
+ -d ${P_R_CLIENTDIR} -v -U -V tls1.0: < ${REQUEST_FILE} \
+ >${TMP}/$HOST.tmp.$$ 2>&1
+ ret=$?
+ cat ${TMP}/$HOST.tmp.$$
+ rm ${TMP}/$HOST.tmp.$$ 2>/dev/null
+
+ html_msg $ret $value "${testname}" \
+ "produced a returncode of $ret, expected is $value"
+ kill_selfserv
+ html "</TABLE><BR>"
+}
+
############################## ssl_stress ##############################
# local shell function to perform SSL stress test
@@ -933,6 +970,9 @@ ssl_run()
"stapling")
ssl_stapling
;;
+ "signed_cert_timestamps")
+ ssl_signed_cert_timestamps
+ ;;
"cov")
ssl_cov
;;