Skip to content

SSL Support #101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .ci/config.ssl.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"Data": {
"ConnectionString": "server=127.0.0.1;user id=ssltest;password=test;port=3306;database=mysqltest;ssl mode=required;certificate file=.ci/ssl-client.pfx;Use Affected Rows=true",
"PasswordlessUser": "no_password",
"SupportsJson": true
}
}
3 changes: 3 additions & 0 deletions .ci/server.cnf
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# uses a seperate key for the server and clinet
# this is how things should be run in production

[mysqld]
ssl-ca=/etc/mysql/conf.d/ssl-ca.pem
ssl-cert=/etc/mysql/conf.d/ssl-server-cert.pem
Expand Down
11 changes: 11 additions & 0 deletions .ci/server.debug
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# uses the same key for the server and client
# uses a non-ephemeral cipher suite
# allows for WireShark to decrypt packets given "ssl-client-key.pem"
# go to "Edit -> Preferences", "Protocols", "SSL", and add RSA key
# this is for testing, don't do this in production

[mysqld]
ssl-ca=/etc/mysql/conf.d/ssl-ca.pem
ssl-cert=/etc/mysql/conf.d/ssl-client-cert.pem
ssl-key=/etc/mysql/conf.d/ssl-client-key.pem
ssl-cipher=AES128-SHA
Binary file added .ci/ssl-client.pfx
Binary file not shown.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ before_install:
- sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893
- sudo apt-get update
- sudo apt-get install -y dotnet-dev-1.0.0-preview2-003121
- cp tests/SideBySide.New/config.json.example tests/SideBySide.New/config.json

script:
- dotnet restore
- dotnet test tests/MySqlConnector.Tests --configuration Release
- dotnet test tests/SideBySide.New --configuration Release
- echo 'Executing tests with ssl mode=none' && cp tests/SideBySide.New/config.json.example tests/SideBySide.New/config.json && dotnet test tests/SideBySide.New --configuration Release
- echo 'Executing tests with ssl mode=required' && cp .ci/config.ssl.json tests/SideBySide.New/config.json && dotnet test tests/SideBySide.New --configuration Release
16 changes: 11 additions & 5 deletions src/MySqlConnector/MySqlClient/ConnectionPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public async Task<MySqlSession> GetSessionAsync(IOBehavior ioBehavior, Cancellat
}

session = new MySqlSession(this, m_generation);
await session.ConnectAsync(m_servers, m_port, m_userId, m_password, m_database, ioBehavior, cancellationToken).ConfigureAwait(false);
await session.ConnectAsync(m_servers, m_port, m_userId, m_password, m_database, m_sslMode, m_certificateFile, m_certificatePassword, ioBehavior, cancellationToken).ConfigureAwait(false);
return session;
}
catch
Expand Down Expand Up @@ -124,8 +124,8 @@ public static ConnectionPool GetPool(MySqlConnectionStringBuilder csb)
ConnectionPool pool;
if (!s_pools.TryGetValue(key, out pool))
{
pool = s_pools.GetOrAdd(key, newKey => new ConnectionPool(csb.Server.Split(','), (int) csb.Port, csb.UserID,
csb.Password, csb.Database, csb.ConnectionReset, (int)csb.MinimumPoolSize, (int) csb.MaximumPoolSize));
pool = s_pools.GetOrAdd(key, newKey => new ConnectionPool(csb.Server.Split(','), (int) csb.Port, csb.UserID, csb.Password, csb.Database,
csb.SslMode, csb.CertificateFile, csb.CertificatePassword, csb.ConnectionReset, (int)csb.MinimumPoolSize, (int) csb.MaximumPoolSize));
}
return pool;
}
Expand All @@ -138,15 +138,18 @@ public static async Task ClearPoolsAsync(IOBehavior ioBehavior, CancellationToke
await pool.ClearAsync(ioBehavior, cancellationToken).ConfigureAwait(false);
}

private ConnectionPool(IEnumerable<string> servers, int port, string userId, string password, string database,
bool resetConnections, int minimumSize, int maximumSize)
private ConnectionPool(IEnumerable<string> servers, int port, string userId, string password, string database, SslMode sslMode,
string certificateFile, string certificatePassword, bool resetConnections, int minimumSize, int maximumSize)
{
m_servers = servers;
m_port = port;
m_userId = userId;
m_password = password;
m_database = database;
m_resetConnections = resetConnections;
m_sslMode = sslMode;
m_certificateFile = certificateFile;
m_certificatePassword = certificatePassword;
m_minimumSize = minimumSize;
m_maximumSize = maximumSize;

Expand All @@ -166,6 +169,9 @@ private ConnectionPool(IEnumerable<string> servers, int port, string userId, str
readonly string m_userId;
readonly string m_password;
readonly string m_database;
readonly SslMode m_sslMode;
readonly string m_certificateFile;
readonly string m_certificatePassword;
readonly bool m_resetConnections;
readonly int m_minimumSize;
readonly int m_maximumSize;
Expand Down
4 changes: 2 additions & 2 deletions src/MySqlConnector/MySqlClient/MySqlConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ private async Task<MySqlSession> CreateSessionAsync(IOBehavior ioBehavior, Cance
else
{
var session = new MySqlSession();
await session.ConnectAsync(m_connectionStringBuilder.Server.Split(','), (int) m_connectionStringBuilder.Port, m_connectionStringBuilder.UserID,
m_connectionStringBuilder.Password, m_connectionStringBuilder.Database, ioBehavior, linkedSource.Token).ConfigureAwait(false);
await session.ConnectAsync(m_connectionStringBuilder.Server.Split(','), (int) m_connectionStringBuilder.Port, m_connectionStringBuilder.UserID, m_connectionStringBuilder.Password, m_connectionStringBuilder.Database,
m_connectionStringBuilder.SslMode, m_connectionStringBuilder.CertificateFile, m_connectionStringBuilder.CertificatePassword, ioBehavior, linkedSource.Token).ConfigureAwait(false);
return session;
}
}
Expand Down
51 changes: 51 additions & 0 deletions src/MySqlConnector/MySqlClient/MySqlConnectionStringBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@

namespace MySql.Data.MySqlClient
{
public enum SslMode
{
None,
Required,
VerifyCa,
VerifyFull,
}

public sealed class MySqlConnectionStringBuilder : DbConnectionStringBuilder
{
public MySqlConnectionStringBuilder()
Expand Down Expand Up @@ -82,6 +90,24 @@ public bool UseCompression
set { MySqlConnectionStringOption.UseCompression.SetValue(this, value); }
}

public SslMode SslMode
{
get { return MySqlConnectionStringOption.SslMode.GetValue(this); }
set { MySqlConnectionStringOption.SslMode.SetValue(this, value); }
}

public string CertificateFile
{
get { return MySqlConnectionStringOption.CertificateFile.GetValue(this); }
set { MySqlConnectionStringOption.CertificateFile.SetValue(this, value); }
}

public string CertificatePassword
{
get { return MySqlConnectionStringOption.CertificatePassword.GetValue(this); }
set { MySqlConnectionStringOption.CertificatePassword.SetValue(this, value); }
}

public bool Pooling
{
get { return MySqlConnectionStringOption.Pooling.GetValue(this); }
Expand Down Expand Up @@ -179,6 +205,9 @@ internal abstract class MySqlConnectionStringOption
public static readonly MySqlConnectionStringOption<bool> OldGuids;
public static readonly MySqlConnectionStringOption<bool> PersistSecurityInfo;
public static readonly MySqlConnectionStringOption<bool> UseCompression;
public static readonly MySqlConnectionStringOption<SslMode> SslMode;
public static readonly MySqlConnectionStringOption<string> CertificateFile;
public static readonly MySqlConnectionStringOption<string> CertificatePassword;
public static readonly MySqlConnectionStringOption<bool> Pooling;
public static readonly MySqlConnectionStringOption<bool> ConnectionReset;
public static readonly MySqlConnectionStringOption<uint> ConnectionTimeout;
Expand Down Expand Up @@ -265,6 +294,18 @@ static MySqlConnectionStringOption()
keys: new[] { "Compress", "Use Compression", "UseCompression" },
defaultValue: false));

AddOption(CertificateFile = new MySqlConnectionStringOption<string>(
keys: new[] { "CertificateFile", "Certificate File" },
defaultValue: ""));

AddOption(CertificatePassword = new MySqlConnectionStringOption<string>(
keys: new[] { "CertificatePassword", "Certificate Password" },
defaultValue: ""));

AddOption(SslMode = new MySqlConnectionStringOption<SslMode>(
keys: new[] { "SSL Mode", "SslMode" },
defaultValue: MySqlClient.SslMode.None));

AddOption(Pooling = new MySqlConnectionStringOption<bool>(
keys: new[] { "Pooling" },
defaultValue: true));
Expand Down Expand Up @@ -334,6 +375,16 @@ private static T ChangeType(object objectValue)
return (T) (object) false;
}

if (typeof(T) == typeof(SslMode) && objectValue is string)
{
foreach (var val in Enum.GetValues(typeof(T)))
{
if (string.Equals((string) objectValue, val.ToString(), StringComparison.OrdinalIgnoreCase))
return (T) val;
}
throw new InvalidOperationException("Value '{0}' not supported for option '{1}'.".FormatInvariant(objectValue, typeof(T).Name));
}

return (T) Convert.ChangeType(objectValue, typeof(T), CultureInfo.InvariantCulture);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace MySql.Data.Protocol.Serialization
{
internal interface IPacketHandler
{
void SetByteHandler(IByteHandler byteHandler);
ValueTask<Packet> ReadPacketAsync(ProtocolErrorBehavior protocolErrorBehavior, IOBehavior ioBehavior);
ValueTask<int> WritePacketAsync(Packet packet, IOBehavior ioBehavior);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace MySql.Data.Protocol.Serialization
{
internal interface IPayloadHandler
{
void SetByteHandler(IByteHandler byteHandler);
ValueTask<ArraySegment<byte>> ReadPayloadAsync(IConversation conversation, ProtocolErrorBehavior protocolErrorBehavior, IOBehavior ioBehavior);
ValueTask<int> WritePayloadAsync(IConversation conversation, ArraySegment<byte> payload, IOBehavior ioBehavior);
}
Expand Down
8 changes: 7 additions & 1 deletion src/MySqlConnector/Protocol/Serialization/PacketHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@ namespace MySql.Data.Protocol.Serialization
{
internal sealed class PacketHandler : IPacketHandler
{

public PacketHandler(IByteHandler byteHandler)
{
m_byteHandler = byteHandler;
m_buffer = new byte[16384];
}

public void SetByteHandler(IByteHandler byteHandler)
{
m_byteHandler = byteHandler;
}

public ValueTask<Packet> ReadPacketAsync(ProtocolErrorBehavior protocolErrorBehavior, IOBehavior ioBehavior)
{
return ReadBytesAsync(0, m_buffer, 0, 4, ioBehavior)
Expand Down Expand Up @@ -73,7 +79,7 @@ private ValueTask<int> ReadBytesAsync(int previousBytesRead, byte[] buffer, int
});
}

private readonly IByteHandler m_byteHandler;
private IByteHandler m_byteHandler;
private readonly byte[] m_buffer;
}
}
5 changes: 5 additions & 0 deletions src/MySqlConnector/Protocol/Serialization/PayloadHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ public PayloadHandler(IPacketHandler packetHandler)
m_packetHandler = packetHandler;
}

public void SetByteHandler(IByteHandler byteHandler)
{
m_packetHandler.SetByteHandler(byteHandler);
}

public ValueTask<ArraySegment<byte>> ReadPayloadAsync(IConversation conversation, ProtocolErrorBehavior protocolErrorBehavior, IOBehavior ioBehavior) =>
ReadPayloadAsync(default(ArraySegment<byte>), conversation, protocolErrorBehavior, ioBehavior);

Expand Down
9 changes: 4 additions & 5 deletions src/MySqlConnector/Protocol/Serialization/SslByteHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@

namespace MySql.Data.Protocol.Serialization
{
internal class SslByteHandler : IByteHandler
internal sealed class SslByteHandler : IByteHandler
{
private readonly SslStream m_sslStream;

public SslByteHandler(SslStream sslStream)
{
m_sslStream = sslStream;
Expand All @@ -23,7 +21,7 @@ public ValueTask<int> ReadBytesAsync(byte[] buffer, int offset, int count, IOBeh

public async Task<int> DoReadBytesAsync(byte[] buffer, int offset, int count)
{
return await m_sslStream.ReadAsync(buffer, offset, count);
return await m_sslStream.ReadAsync(buffer, offset, count).ConfigureAwait(false);
}

public ValueTask<int> WriteBytesAsync(ArraySegment<byte> payload, IOBehavior ioBehavior)
Expand All @@ -41,9 +39,10 @@ public ValueTask<int> WriteBytesAsync(ArraySegment<byte> payload, IOBehavior ioB

private async Task<int> DoWriteBytesAsync(ArraySegment<byte> payload)
{
await m_sslStream.ReadAsync(payload.Array, payload.Offset, payload.Count);
await m_sslStream.WriteAsync(payload.Array, payload.Offset, payload.Count).ConfigureAwait(false);
return default(int);
}

private readonly SslStream m_sslStream;
}
}
25 changes: 20 additions & 5 deletions src/MySqlConnector/Serialization/HandshakeResponse41Packet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ namespace MySql.Data.Serialization
{
internal sealed class HandshakeResponse41Packet
{
public static byte[] Create(InitialHandshakePacket handshake, string userName, string password, string database)
internal static PayloadWriter CapabilitiesPayload(string database, ProtocolCapabilities additionalCapabilities=0)
{
// TODO: verify server capabilities

var writer = new PayloadWriter();

writer.WriteInt32((int) (
Expand All @@ -21,12 +19,29 @@ public static byte[] Create(InitialHandshakePacket handshake, string userName, s
ProtocolCapabilities.MultiStatements |
ProtocolCapabilities.MultiResults |
ProtocolCapabilities.PreparedStatementMultiResults |
(string.IsNullOrWhiteSpace(database) ? 0 : ProtocolCapabilities.ConnectWithDatabase)));
(string.IsNullOrWhiteSpace(database) ? 0 : ProtocolCapabilities.ConnectWithDatabase) |
additionalCapabilities));
writer.WriteInt32(0x40000000);
writer.WriteByte((byte) CharacterSet.Utf8Mb4Binary);
writer.Write(new byte[23]);
writer.WriteNullTerminatedString(userName);

return writer;
}

public static byte[] InitSsl(string database)
{
return CapabilitiesPayload(
database,
ProtocolCapabilities.Ssl
).ToBytes();
}

public static byte[] Create(InitialHandshakePacket handshake, string userName, string password, string database)
{
// TODO: verify server capabilities

var writer = CapabilitiesPayload(database);
writer.WriteNullTerminatedString(userName);
var authenticationResponse = AuthenticationUtility.CreateAuthenticationResponse(handshake.AuthPluginData, 0, password);
writer.WriteByte((byte) authenticationResponse.Length);
writer.Write(authenticationResponse);
Expand Down
Loading