mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-30 00:04:49 -04:00 
			
		
		
		
	Implement channel binding tls-server-end-point for SCRAM
This adds a second standard channel binding type for SCRAM. It is mainly intended for third-party clients that cannot implement tls-unique, for example JDBC. Author: Michael Paquier <michael.paquier@gmail.com>
This commit is contained in:
		
							parent
							
								
									39cfe86195
								
							
						
					
					
						commit
						d3fb72ea6d
					
				| @ -1575,9 +1575,13 @@ the password is in. | ||||
| 
 | ||||
|   <para> | ||||
| <firstterm>Channel binding</firstterm> is supported in PostgreSQL builds with | ||||
| SSL support. The SASL mechanism name for SCRAM with channel binding | ||||
| is <literal>SCRAM-SHA-256-PLUS</literal>.  The only channel binding type | ||||
| supported at the moment is <literal>tls-unique</literal>, defined in RFC 5929. | ||||
| SSL support. The SASL mechanism name for SCRAM with channel binding is | ||||
| <literal>SCRAM-SHA-256-PLUS</literal>.  Two channel binding types are | ||||
| supported: <literal>tls-unique</literal> and | ||||
| <literal>tls-server-end-point</literal>, both defined in RFC 5929.  Clients | ||||
| should use <literal>tls-unique</literal> if they can support it. | ||||
| <literal>tls-server-end-point</literal> is intended for third-party clients | ||||
| that cannot support <literal>tls-unique</literal> for some reason. | ||||
|   </para> | ||||
| 
 | ||||
| <procedure> | ||||
| @ -1597,9 +1601,10 @@ supported at the moment is <literal>tls-unique</literal>, defined in RFC 5929. | ||||
|   indicates the chosen mechanism, <literal>SCRAM-SHA-256</literal> or | ||||
|   <literal>SCRAM-SHA-256-PLUS</literal>. (A client is free to choose either | ||||
|   mechanism, but for better security it should choose the channel-binding | ||||
|   variant if it can support it.) In the Initial Client response field, | ||||
|   the message contains the SCRAM | ||||
|   <structname>client-first-message</structname>. | ||||
|   variant if it can support it.) In the Initial Client response field, the | ||||
|   message contains the SCRAM <structname>client-first-message</structname>. | ||||
|   The <structname>client-first-message</structname> also contains the channel | ||||
|   binding type chosen by the client. | ||||
| </para> | ||||
| </step> | ||||
| <step id="scram-server-first"> | ||||
|  | ||||
| @ -849,13 +849,14 @@ read_client_first_message(scram_state *state, char *input) | ||||
| 				} | ||||
| 
 | ||||
| 				/*
 | ||||
| 				 * Read value provided by client; only tls-unique is supported | ||||
| 				 * for now.  (It is not safe to print the name of an | ||||
| 				 * unsupported binding type in the error message.  Pranksters | ||||
| 				 * could print arbitrary strings into the log that way.) | ||||
| 				 * Read value provided by client.  (It is not safe to print | ||||
| 				 * the name of an unsupported binding type in the error | ||||
| 				 * message.  Pranksters could print arbitrary strings into the | ||||
| 				 * log that way.) | ||||
| 				 */ | ||||
| 				channel_binding_type = read_attr_value(&input, 'p'); | ||||
| 				if (strcmp(channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_UNIQUE) != 0) | ||||
| 				if (strcmp(channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_UNIQUE) != 0 && | ||||
| 					strcmp(channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_END_POINT) != 0) | ||||
| 					ereport(ERROR, | ||||
| 							(errcode(ERRCODE_PROTOCOL_VIOLATION), | ||||
| 							 (errmsg("unsupported SCRAM channel-binding type")))); | ||||
| @ -1114,6 +1115,15 @@ read_client_final_message(scram_state *state, char *input) | ||||
| 		{ | ||||
| #ifdef USE_SSL | ||||
| 			cbind_data = be_tls_get_peer_finished(state->port, &cbind_data_len); | ||||
| #endif | ||||
| 		} | ||||
| 		else if (strcmp(state->channel_binding_type, | ||||
| 						SCRAM_CHANNEL_BINDING_TLS_END_POINT) == 0) | ||||
| 		{ | ||||
| 			/* Fetch hash data of server's SSL certificate */ | ||||
| #ifdef USE_SSL | ||||
| 			cbind_data = be_tls_get_certificate_hash(state->port, | ||||
| 													 &cbind_data_len); | ||||
| #endif | ||||
| 		} | ||||
| 		else | ||||
|  | ||||
| @ -1239,6 +1239,67 @@ be_tls_get_peer_finished(Port *port, size_t *len) | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Get the server certificate hash for SCRAM channel binding type | ||||
|  * tls-server-end-point. | ||||
|  * | ||||
|  * The result is a palloc'd hash of the server certificate with its | ||||
|  * size, and NULL if there is no certificate available. | ||||
|  */ | ||||
| char * | ||||
| be_tls_get_certificate_hash(Port *port, size_t *len) | ||||
| { | ||||
| 	X509	   *server_cert; | ||||
| 	char	   *cert_hash; | ||||
| 	const EVP_MD *algo_type = NULL; | ||||
| 	unsigned char hash[EVP_MAX_MD_SIZE];	/* size for SHA-512 */ | ||||
| 	unsigned int hash_size; | ||||
| 	int			algo_nid; | ||||
| 
 | ||||
| 	*len = 0; | ||||
| 	server_cert = SSL_get_certificate(port->ssl); | ||||
| 	if (server_cert == NULL) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Get the signature algorithm of the certificate to determine the | ||||
| 	 * hash algorithm to use for the result. | ||||
| 	 */ | ||||
| 	if (!OBJ_find_sigid_algs(X509_get_signature_nid(server_cert), | ||||
| 							 &algo_nid, NULL)) | ||||
| 		elog(ERROR, "could not determine server certificate signature algorithm"); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * The TLS server's certificate bytes need to be hashed with SHA-256 if | ||||
| 	 * its signature algorithm is MD5 or SHA-1 as per RFC 5929 | ||||
| 	 * (https://tools.ietf.org/html/rfc5929#section-4.1).  If something else
 | ||||
| 	 * is used, the same hash as the signature algorithm is used. | ||||
| 	 */ | ||||
| 	switch (algo_nid) | ||||
| 	{ | ||||
| 		case NID_md5: | ||||
| 		case NID_sha1: | ||||
| 			algo_type = EVP_sha256(); | ||||
| 			break; | ||||
| 		default: | ||||
| 			algo_type = EVP_get_digestbynid(algo_nid); | ||||
| 			if (algo_type == NULL) | ||||
| 				elog(ERROR, "could not find digest for NID %s", | ||||
| 					 OBJ_nid2sn(algo_nid)); | ||||
| 			break; | ||||
| 	} | ||||
| 
 | ||||
| 	/* generate and save the certificate hash */ | ||||
| 	if (!X509_digest(server_cert, algo_type, hash, &hash_size)) | ||||
| 		elog(ERROR, "could not generate server certificate hash"); | ||||
| 
 | ||||
| 	cert_hash = palloc(hash_size); | ||||
| 	memcpy(cert_hash, hash, hash_size); | ||||
| 	*len = hash_size; | ||||
| 
 | ||||
| 	return cert_hash; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Convert an X509 subject name to a cstring. | ||||
|  * | ||||
|  | ||||
| @ -21,6 +21,7 @@ | ||||
| 
 | ||||
| /* Channel binding types */ | ||||
| #define SCRAM_CHANNEL_BINDING_TLS_UNIQUE    "tls-unique" | ||||
| #define SCRAM_CHANNEL_BINDING_TLS_END_POINT	"tls-server-end-point" | ||||
| 
 | ||||
| /* Length of SCRAM keys (client and server) */ | ||||
| #define SCRAM_KEY_LEN				PG_SHA256_DIGEST_LENGTH | ||||
|  | ||||
| @ -210,6 +210,7 @@ extern void be_tls_get_version(Port *port, char *ptr, size_t len); | ||||
| extern void be_tls_get_cipher(Port *port, char *ptr, size_t len); | ||||
| extern void be_tls_get_peerdn_name(Port *port, char *ptr, size_t len); | ||||
| extern char *be_tls_get_peer_finished(Port *port, size_t *len); | ||||
| extern char *be_tls_get_certificate_hash(Port *port, size_t *len); | ||||
| #endif | ||||
| 
 | ||||
| extern ProtocolVersion FrontendProtocol; | ||||
|  | ||||
| @ -444,6 +444,21 @@ build_client_final_message(fe_scram_state *state) | ||||
| 			cbind_data = pgtls_get_finished(state->conn, &cbind_data_len); | ||||
| 			if (cbind_data == NULL) | ||||
| 				goto oom_error; | ||||
| #endif | ||||
| 		} | ||||
| 		else if (strcmp(conn->scram_channel_binding, | ||||
| 						SCRAM_CHANNEL_BINDING_TLS_END_POINT) == 0) | ||||
| 		{ | ||||
| 			/* Fetch hash data of server's SSL certificate */ | ||||
| #ifdef USE_SSL | ||||
| 			cbind_data = | ||||
| 				pgtls_get_peer_certificate_hash(state->conn, | ||||
| 												&cbind_data_len); | ||||
| 			if (cbind_data == NULL) | ||||
| 			{ | ||||
| 				/* error message is already set on error */ | ||||
| 				return NULL; | ||||
| 			} | ||||
| #endif | ||||
| 		} | ||||
| 		else | ||||
|  | ||||
| @ -419,6 +419,86 @@ pgtls_get_finished(PGconn *conn, size_t *len) | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Get the hash of the server certificate, for SCRAM channel binding type | ||||
|  * tls-server-end-point. | ||||
|  * | ||||
|  * NULL is sent back to the caller in the event of an error, with an | ||||
|  * error message for the caller to consume. | ||||
|  */ | ||||
| char * | ||||
| pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len) | ||||
| { | ||||
| 	X509	   *peer_cert; | ||||
| 	const EVP_MD *algo_type; | ||||
| 	unsigned char hash[EVP_MAX_MD_SIZE];	/* size for SHA-512 */ | ||||
| 	unsigned int hash_size; | ||||
| 	int			algo_nid; | ||||
| 	char	   *cert_hash; | ||||
| 
 | ||||
| 	*len = 0; | ||||
| 
 | ||||
| 	if (!conn->peer) | ||||
| 		return NULL; | ||||
| 
 | ||||
| 	peer_cert = conn->peer; | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * Get the signature algorithm of the certificate to determine the hash | ||||
| 	 * algorithm to use for the result. | ||||
| 	 */ | ||||
| 	if (!OBJ_find_sigid_algs(X509_get_signature_nid(peer_cert), | ||||
| 							 &algo_nid, NULL)) | ||||
| 	{ | ||||
| 		printfPQExpBuffer(&conn->errorMessage, | ||||
| 						  libpq_gettext("could not determine server certificate signature algorithm\n")); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	/*
 | ||||
| 	 * The TLS server's certificate bytes need to be hashed with SHA-256 if | ||||
| 	 * its signature algorithm is MD5 or SHA-1 as per RFC 5929 | ||||
| 	 * (https://tools.ietf.org/html/rfc5929#section-4.1).  If something else
 | ||||
| 	 * is used, the same hash as the signature algorithm is used. | ||||
| 	 */ | ||||
| 	switch (algo_nid) | ||||
| 	{ | ||||
| 		case NID_md5: | ||||
| 		case NID_sha1: | ||||
| 			algo_type = EVP_sha256(); | ||||
| 			break; | ||||
| 		default: | ||||
| 			algo_type = EVP_get_digestbynid(algo_nid); | ||||
| 			if (algo_type == NULL) | ||||
| 			{ | ||||
| 				printfPQExpBuffer(&conn->errorMessage, | ||||
| 								  libpq_gettext("could not find digest for NID %s\n"), | ||||
| 								  OBJ_nid2sn(algo_nid)); | ||||
| 				return NULL; | ||||
| 			} | ||||
| 			break; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!X509_digest(peer_cert, algo_type, hash, &hash_size)) | ||||
| 	{ | ||||
| 		printfPQExpBuffer(&conn->errorMessage, | ||||
| 						  libpq_gettext("could not generate peer certificate hash\n")); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 
 | ||||
| 	/* save result */ | ||||
| 	cert_hash = malloc(hash_size); | ||||
| 	if (cert_hash == NULL) | ||||
| 	{ | ||||
| 		printfPQExpBuffer(&conn->errorMessage, | ||||
| 						  libpq_gettext("out of memory\n")); | ||||
| 		return NULL; | ||||
| 	} | ||||
| 	memcpy(cert_hash, hash, hash_size); | ||||
| 	*len = hash_size; | ||||
| 
 | ||||
| 	return cert_hash; | ||||
| } | ||||
| 
 | ||||
| /* ------------------------------------------------------------ */ | ||||
| /*						OpenSSL specific code					*/ | ||||
|  | ||||
| @ -672,6 +672,7 @@ extern ssize_t pgtls_read(PGconn *conn, void *ptr, size_t len); | ||||
| extern bool pgtls_read_pending(PGconn *conn); | ||||
| extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len); | ||||
| extern char *pgtls_get_finished(PGconn *conn, size_t *len); | ||||
| extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len); | ||||
| 
 | ||||
| /*
 | ||||
|  * this is so that we can check if a connection is non-blocking internally | ||||
|  | ||||
| @ -4,7 +4,7 @@ use strict; | ||||
| use warnings; | ||||
| use PostgresNode; | ||||
| use TestLib; | ||||
| use Test::More tests => 4; | ||||
| use Test::More tests => 5; | ||||
| use ServerSetup; | ||||
| use File::Copy; | ||||
| 
 | ||||
| @ -45,6 +45,9 @@ test_connect_ok($common_connstr, | ||||
| test_connect_ok($common_connstr, | ||||
| 	"scram_channel_binding=''", | ||||
| 	"SCRAM authentication without channel binding"); | ||||
| test_connect_ok($common_connstr, | ||||
| 	"scram_channel_binding=tls-server-end-point", | ||||
| 	"SCRAM authentication with tls-server-end-point as channel binding"); | ||||
| test_connect_fails($common_connstr, | ||||
| 	"scram_channel_binding=not-exists", | ||||
| 	"SCRAM authentication with invalid channel binding"); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user