@@ -320,6 +320,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
320320 "Require-Peer" , "" , 10 ,
321321 offsetof(struct pg_conn , requirepeer )},
322322
323+ {"sslminprotocolversion" , "PGSSLMINPROTOCOLVERSION" , NULL , NULL ,
324+ "SSL-Minimum-Protocol-Version" , "" , 8 , /* sizeof("TLSv1.x") == 8 */
325+ offsetof(struct pg_conn , sslminprotocolversion )},
326+
327+ {"sslmaxprotocolversion" , "PGSSLMAXPROTOCOLVERSION" , NULL , NULL ,
328+ "SSL-Maximum-Protocol-Version" , "" , 8 , /* sizeof("TLSv1.x") == 8 */
329+ offsetof(struct pg_conn , sslmaxprotocolversion )},
330+
323331 /*
324332 * As with SSL, all GSS options are exposed even in builds that don't have
325333 * support.
@@ -426,6 +434,8 @@ static char *passwordFromFile(const char *hostname, const char *port, const char
426434 const char * username , const char * pgpassfile );
427435static void pgpassfileWarning (PGconn * conn );
428436static void default_threadlock (int acquire );
437+ static bool sslVerifyProtocolVersion (const char * version );
438+ static bool sslVerifyProtocolRange (const char * min , const char * max );
429439
430440
431441/* global variable because fe-auth.c needs to access it */
@@ -1285,6 +1295,40 @@ connectOptions2(PGconn *conn)
12851295 goto oom_error ;
12861296 }
12871297
1298+ /*
1299+ * Validate TLS protocol versions for sslminprotocolversion and
1300+ * sslmaxprotocolversion.
1301+ */
1302+ if (!sslVerifyProtocolVersion (conn -> sslminprotocolversion ))
1303+ {
1304+ printfPQExpBuffer (& conn -> errorMessage ,
1305+ libpq_gettext ("invalid sslminprotocolversion value: \"%s\"\n" ),
1306+ conn -> sslminprotocolversion );
1307+ return false;
1308+ }
1309+ if (!sslVerifyProtocolVersion (conn -> sslmaxprotocolversion ))
1310+ {
1311+ printfPQExpBuffer (& conn -> errorMessage ,
1312+ libpq_gettext ("invalid sslmaxprotocolversion value: \"%s\"\n" ),
1313+ conn -> sslmaxprotocolversion );
1314+ return false;
1315+ }
1316+
1317+ /*
1318+ * Check if the range of SSL protocols defined is correct. This is done
1319+ * at this early step because this is independent of the SSL
1320+ * implementation used, and this avoids unnecessary cycles with an
1321+ * already-built SSL context when the connection is being established, as
1322+ * it would be doomed anyway.
1323+ */
1324+ if (!sslVerifyProtocolRange (conn -> sslminprotocolversion ,
1325+ conn -> sslmaxprotocolversion ))
1326+ {
1327+ printfPQExpBuffer (& conn -> errorMessage ,
1328+ libpq_gettext ("invalid SSL protocol version range" ));
1329+ return false;
1330+ }
1331+
12881332 /*
12891333 * validate gssencmode option
12901334 */
@@ -4001,6 +4045,10 @@ freePGconn(PGconn *conn)
40014045 free (conn -> sslcompression );
40024046 if (conn -> requirepeer )
40034047 free (conn -> requirepeer );
4048+ if (conn -> sslminprotocolversion )
4049+ free (conn -> sslminprotocolversion );
4050+ if (conn -> sslmaxprotocolversion )
4051+ free (conn -> sslmaxprotocolversion );
40044052 if (conn -> gssencmode )
40054053 free (conn -> gssencmode );
40064054 if (conn -> krbsrvname )
@@ -7031,6 +7079,71 @@ pgpassfileWarning(PGconn *conn)
70317079 }
70327080}
70337081
7082+ /*
7083+ * Check if the SSL procotol value given in input is valid or not.
7084+ * This is used as a sanity check routine for the connection parameters
7085+ * sslminprotocolversion and sslmaxprotocolversion.
7086+ */
7087+ static bool
7088+ sslVerifyProtocolVersion (const char * version )
7089+ {
7090+ /*
7091+ * An empty string and a NULL value are considered valid as it is
7092+ * equivalent to ignoring the parameter.
7093+ */
7094+ if (!version || strlen (version ) == 0 )
7095+ return true;
7096+
7097+ if (pg_strcasecmp (version , "TLSv1" ) == 0 ||
7098+ pg_strcasecmp (version , "TLSv1.1" ) == 0 ||
7099+ pg_strcasecmp (version , "TLSv1.2" ) == 0 ||
7100+ pg_strcasecmp (version , "TLSv1.3" ) == 0 )
7101+ return true;
7102+
7103+ /* anything else is wrong */
7104+ return false;
7105+ }
7106+
7107+
7108+ /*
7109+ * Ensure that the SSL protocol range given in input is correct. The check
7110+ * is performed on the input string to keep it TLS backend agnostic. Input
7111+ * to this function is expected verified with sslVerifyProtocolVersion().
7112+ */
7113+ static bool
7114+ sslVerifyProtocolRange (const char * min , const char * max )
7115+ {
7116+ Assert (sslVerifyProtocolVersion (min ) &&
7117+ sslVerifyProtocolVersion (max ));
7118+
7119+ /* If at least one of the bounds is not set, the range is valid */
7120+ if (min == NULL || max == NULL || strlen (min ) == 0 || strlen (max ) == 0 )
7121+ return true;
7122+
7123+ /*
7124+ * If the minimum version is the lowest one we accept, then all options
7125+ * for the maximum are valid.
7126+ */
7127+ if (pg_strcasecmp (min , "TLSv1" ) == 0 )
7128+ return true;
7129+
7130+ /*
7131+ * The minimum bound is valid, and cannot be TLSv1, so using TLSv1 for the
7132+ * maximum is incorrect.
7133+ */
7134+ if (pg_strcasecmp (max , "TLSv1" ) == 0 )
7135+ return false;
7136+
7137+ /*
7138+ * At this point we know that we have a mix of TLSv1.1 through 1.3
7139+ * versions.
7140+ */
7141+ if (pg_strcasecmp (min , max ) > 0 )
7142+ return false;
7143+
7144+ return true;
7145+ }
7146+
70347147
70357148/*
70367149 * Obtain user's home directory, return in given buffer
0 commit comments