@@ -320,6 +320,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
320
320
"Require-Peer" , "" , 10 ,
321
321
offsetof(struct pg_conn , requirepeer )},
322
322
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
+
323
331
/*
324
332
* As with SSL, all GSS options are exposed even in builds that don't have
325
333
* support.
@@ -426,6 +434,8 @@ static char *passwordFromFile(const char *hostname, const char *port, const char
426
434
const char * username , const char * pgpassfile );
427
435
static void pgpassfileWarning (PGconn * conn );
428
436
static void default_threadlock (int acquire );
437
+ static bool sslVerifyProtocolVersion (const char * version );
438
+ static bool sslVerifyProtocolRange (const char * min , const char * max );
429
439
430
440
431
441
/* global variable because fe-auth.c needs to access it */
@@ -1285,6 +1295,40 @@ connectOptions2(PGconn *conn)
1285
1295
goto oom_error ;
1286
1296
}
1287
1297
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
+
1288
1332
/*
1289
1333
* validate gssencmode option
1290
1334
*/
@@ -4001,6 +4045,10 @@ freePGconn(PGconn *conn)
4001
4045
free (conn -> sslcompression );
4002
4046
if (conn -> requirepeer )
4003
4047
free (conn -> requirepeer );
4048
+ if (conn -> sslminprotocolversion )
4049
+ free (conn -> sslminprotocolversion );
4050
+ if (conn -> sslmaxprotocolversion )
4051
+ free (conn -> sslmaxprotocolversion );
4004
4052
if (conn -> gssencmode )
4005
4053
free (conn -> gssencmode );
4006
4054
if (conn -> krbsrvname )
@@ -7031,6 +7079,71 @@ pgpassfileWarning(PGconn *conn)
7031
7079
}
7032
7080
}
7033
7081
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
+
7034
7147
7035
7148
/*
7036
7149
* Obtain user's home directory, return in given buffer
0 commit comments