Skip to content
Permalink
Browse files

New ways to provide client certificates

* Client Certificate connection string parameter
* PGSSLCERT environment variable
* ~/.postgresql/postgresql.crt

Closes #2129
  • Loading branch information...
roji committed Aug 30, 2019
1 parent 552d8d8 commit b8e9388726272256fc462b3f2e3e97c11ee4b7e5
@@ -21,7 +21,7 @@ Below are the connection string parameters which Npgsql understands.
|--------------------------|-------------------------------------------------------------------------|------------------|
| SSL Mode | Controls whether SSL is used, depending on server support. Can be `Require`, `Disable`, or `Prefer`. [See docs for more info](security.md). | Disable |
| Trust Server Certificate | Whether to trust the server certificate without validating it. [See docs for more info](security.md). | false |
| Use SSL Stream | Npgsql uses its own internal implementation of TLS/SSL. Turn this on to use .NET SslStream instead. | false |
| Client Certificate | Location of a client certificate to be sent to the server. | [See docs](security.md) |
| Check Certificate Revocation | Whether to check the certificate revocation list during authentication. False by default. | false |
| Integrated Security | Whether to use integrated security to log in (GSS/SSPI), currently supported on Windows only. [See docs for more info](security.md). | false |
| Persist Security Info | Gets or sets a Boolean value that indicates if security-sensitive information, such as the password, is not returned as part of the connection if the connection is open or has ever been in an open state. Since 3.1 only. | false |
@@ -15,6 +15,7 @@ The major new features of 4.1 are:
* Support for the new async methods introduced in .NET Standard 2.1 ([#2481](https://github.com/npgsql/npgsql/issues/2481)).
* Expose performance statistics via [the new .NET event counters](https://devblogs.microsoft.com/dotnet/introducing-diagnostics-improvements-in-net-core-3-0/) ([#1725](https://github.com/npgsql/npgsql/issues/1725)).
* Async support for binary imports and exports ([#1632](https://github.com/npgsql/npgsql/issues/1632)).
* Easier and PostgreSQL standard ways to provide client certificates for authentication ([#2129](https://github.com/npgsql/npgsql/issues/2129)).

Many other small improvements and performance optimizations have been introduced as well - you can track progress [here](https://github.com/npgsql/npgsql/issues?utf8=%E2%9C%93&q=milestone%3A4.1).

@@ -20,11 +20,8 @@ Once your PostgreSQL is configured correctly, simply include `Integrated Securit

## Encryption (SSL/TLS)

By default PostgreSQL connections are unencrypted, but you can turn on SSL/TLS encryption if you wish. First, you have to set up your PostgreSQL to receive SSL/TLS connections [as described here](http://www.postgresql.org/docs/current/static/ssl-tcp.html).
By default PostgreSQL connections are unencrypted, but you can turn on SSL/TLS encryption if you wish. First, you have to set up your PostgreSQL to receive SSL/TLS connections [as described here](http://www.postgresql.org/docs/current/static/ssl-tcp.html). Once that's done, specify `SSL Mode` in your connection string, setting it to either `Require` (connection will fail if the server isn't set up for encryption), or `Prefer` (use encryption if possible but fallback to unencrypted otherwise).

Once that's done, specify `SSL Mode` in your connection string, setting it to either `Require` (connection will fail if the server isn't set up for encryption), or `Prefer` (use encryption if possible but fallback to unencrypted otherwise).
By default, Npgsql will validate your server's certificate; if you're using a self-signed certificate, this will fail. You can instruct Npgsql to ignore this by specifying `Trust Server Certificate=true` in the connection string. To precisely control how the server's certificate is validated, you can register `UserCertificateValidationCallback` on `NpgsqlConnection` (this works just like on .NET's [`SslStream`](https://docs.microsoft.com/en-us/dotnet/api/system.net.security.sslstream)).

Note that by default, Npgsql will verify that your server's certificate is valid. If you're using a self-signed certificate this will fail. You can instruct Npgsql to ignore this by specifying
`Trust Server Certificate=true` in the connection string. To precisely control how the server's certificate is validated, you can register `UserCertificateValidationCallback` on `NpgsqlConnection` (this works just like on .NET's [`SSLStream`](https://msdn.microsoft.com/en-us/library/system.net.security.remotecertificatevalidationcallback(v=vs.110).aspx)).

You can also have Npgsql provide client certificates to the server by registering the `ProvideClientCertificatesCallback` on `NpgsqlConnection` (this works just like on .NET's [`SSLStream`](https://msdn.microsoft.com/en-us/library/system.net.security.localcertificateselectioncallback(v=vs.110).aspx)).
If you need provide client certificates as part of authentication, you can point Npgsql to a certificate file by setting the `Client Certificate` connection string parameter. Npgsql will also implement [the standard PostgreSQL behavior](https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-CLIENTCERT) and will recognize the `PGSSLCERT` environment variable, as well as `~/.postgresql/postgresql.crt` (`%APPDATA%\postgresql\postgresql.crt` on Windows). Finally, you can set the `ProvideClientCertificatesCallback` on `NpgsqlConnection` to further customize how client certificates are provided (this works just like on .NET's [`SslStream`](https://docs.microsoft.com/en-us/dotnet/api/system.net.security.sslstream)).
@@ -505,14 +505,14 @@ public string Encoding
[NpgsqlConnectionStringProperty]
public SslMode SslMode
{
get => _sslmode;
get => _sslMode;
set
{
_sslmode = value;
_sslMode = value;
SetValue(nameof(SslMode), value);
}
}
SslMode _sslmode;
SslMode _sslMode;

/// <summary>
/// Whether to trust the server certificate without validating it.
@@ -532,6 +532,24 @@ public bool TrustServerCertificate
}
bool _trustServerCertificate;

/// <summary>
/// Location of a client certificate to be sent to the server.
/// </summary>
[Category("Security")]
[Description("Location of a client certificate to be sent to the server.")]
[DisplayName("Client Certificate")]
[NpgsqlConnectionStringProperty]
public string? ClientCertificate
{
get => _clientCertificate;
set
{
_clientCertificate = value;
SetValue(nameof(ClientCertificate), value);
}
}
string? _clientCertificate;

/// <summary>
/// Whether to check the certificate revocation list during authentication.
/// False by default.
@@ -536,12 +536,20 @@ async Task RawOpen(NpgsqlTimeout timeout, bool async, CancellationToken cancella
throw new NpgsqlException($"Received unknown response {response} for SSLRequest (expecting S or N)");
case 'N':
if (SslMode == SslMode.Require)
{
throw new NpgsqlException("SSL connection requested. No SSL enabled connection from this host is configured.");
}
break;
case 'S':
var clientCertificates = new X509CertificateCollection();
if (Settings.ClientCertificate != null)
clientCertificates.Add(new X509Certificate(Settings.ClientCertificate));
else if (Environment.GetEnvironmentVariable("PGSSLCERT") is string envCertPath)
clientCertificates.Add(new X509Certificate(envCertPath));
else if (Environment.GetEnvironmentVariable(PGUtil.IsWindows ? "APPDATA" : "HOME") is string homeDir)
{
var certPath = Path.Combine(homeDir, "postgresql", "postgresql.crt");
if (File.Exists(certPath))
clientCertificates.Add(new X509Certificate(certPath));
}
ProvideClientCertificatesCallback?.Invoke(clientCertificates);

RemoteCertificateValidationCallback certificateValidationCallback;
@@ -552,7 +560,7 @@ async Task RawOpen(NpgsqlTimeout timeout, bool async, CancellationToken cancella
else
certificateValidationCallback = DefaultUserCertificateValidationCallback;

var sslStream = new SslStream(_stream, false, certificateValidationCallback);
var sslStream = new SslStream(_stream, leaveInnerStreamOpen: false, certificateValidationCallback);
if (async)
await sslStream.AuthenticateAsClientAsync(Host, clientCertificates, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12, Settings.CheckCertificateRevocation);
else
@@ -69,15 +69,10 @@ class PgPassFile
/// </remarks>
/// <returns>Path to the pgpass file</returns>
internal static string? GetSystemPgPassFilePath()
{
var pgpassEnv = Environment.GetEnvironmentVariable("PGPASSFILE");
if (pgpassEnv != null)
return pgpassEnv;

return PGUtil.IsWindows
? Environment.GetEnvironmentVariable("APPDATA") is string appData ? Path.Combine(appData, "postgresql", "pgpass.conf") : null
: Environment.GetEnvironmentVariable("HOME") is string home ? Path.Combine(home, "postgresql", "pgpass.conf") : null;
}
=> Environment.GetEnvironmentVariable("PGPASSFILE") ??
(Environment.GetEnvironmentVariable(PGUtil.IsWindows ? "APPDATA" : "HOME") is string appData
? Path.Combine(appData, "postgresql", "pgpass.conf")
: null);

/// <summary>
/// Represents a hostname, port, database, username, and password combination that has been retrieved from a .pgpass file
@@ -32,7 +32,7 @@ protected virtual NpgsqlConnection OpenConnection(string? connectionString = nul
{
if (e.SqlState == PostgresErrorCodes.InvalidCatalogName)
TestUtil.IgnoreExceptOnBuildServer("Please create a database npgsql_tests, owned by user npgsql_tests");
else if (e.SqlState == PostgresErrorCodes.InvalidPassword)
else if (e.SqlState == PostgresErrorCodes.InvalidPassword && connectionString == DefaultConnectionString)
TestUtil.IgnoreExceptOnBuildServer("Please create a user npgsql_tests as follows: create user npgsql_tests with password 'npgsql_tests'");
else
throw;

0 comments on commit b8e9388

Please sign in to comment.
You can’t perform that action at this time.