Permalink
Browse files

Merge branch 'hotfix/3.2.7'

  • Loading branch information...
roji committed Feb 27, 2018
2 parents 57127c8 + addf2b0 commit f26b14af68d631f0855d86b358e5a53888be46e8
@@ -1,5 +1,5 @@
image: Visual Studio 2017 Preview
version: 3.2.6-{build}
version: 3.2.7-{build}
environment:
global:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
@@ -11,7 +11,9 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GSS/@EntryIndexedValue">GSS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MD/@EntryIndexedValue">MD</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OID/@EntryIndexedValue">OID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SASL/@EntryIndexedValue">SASL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SCM/@EntryIndexedValue">SCM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SCRAM/@EntryIndexedValue">SCRAM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SQL/@EntryIndexedValue">SQL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SRID/@EntryIndexedValue">SRID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SSL/@EntryIndexedValue">SSL</s:String>
@@ -21,6 +21,10 @@
// TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#endregion

using System;
using System.Collections.Generic;
using Npgsql.Logging;

namespace Npgsql.BackendMessages
{
abstract class AuthenticationRequestMessage : IBackendMessage
@@ -116,6 +120,106 @@ class AuthenticationSSPIMessage : AuthenticationRequestMessage
AuthenticationSSPIMessage() { }
}

#region SASL

class AuthenticationSASLMessage : AuthenticationRequestMessage
{
internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationSASL;
internal List<string> Mechanisms { get; } = new List<string>();

internal AuthenticationSASLMessage(ReadBuffer buf)
{
while (buf.Buffer[buf.ReadPosition] != 0)
Mechanisms.Add(buf.ReadNullTerminatedString());
buf.ReadByte();
if (Mechanisms.Count == 0)
throw new NpgsqlException("Received AuthenticationSASL message with 0 mechanisms!");
}
}

class AuthenticationSASLContinueMessage : AuthenticationRequestMessage
{
internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationSASLContinue;
internal byte[] Payload { get; }

internal AuthenticationSASLContinueMessage(ReadBuffer buf, int len)
{
Payload = new byte[len];
buf.ReadBytes(Payload, 0, len);
}
}

class AuthenticationSCRAMServerFirstMessage
{
static readonly NpgsqlLogger Log = NpgsqlLogManager.GetCurrentClassLogger();

internal string Nonce { get; }
internal string Salt { get; }
internal int Iteration { get; } = -1;

internal AuthenticationSCRAMServerFirstMessage(byte[] bytes)
{
var data = PGUtil.UTF8Encoding.GetString(bytes);

foreach (var part in data.Split(','))
{
if (part.StartsWith("r="))
Nonce = part.Substring(2);
else if (part.StartsWith("s="))
Salt = part.Substring(2);
else if (part.StartsWith("i="))
Iteration = int.Parse(part.Substring(2));
else
Log.Debug("Unknown part in SCRAM server-first message:" + part);
}

if (Nonce == null)
throw new NpgsqlException("Server nonce not received in SCRAM server-first message");
if (Salt == null)
throw new NpgsqlException("Server salt not received in SCRAM server-first message");
if (Iteration == -1)
throw new NpgsqlException("Server iterations not received in SCRAM server-first message");
}
}

class AuthenticationSASLFinalMessage : AuthenticationRequestMessage
{
internal override AuthenticationRequestType AuthRequestType => AuthenticationRequestType.AuthenticationSASLFinal;
internal byte[] Payload { get; }

internal AuthenticationSASLFinalMessage(ReadBuffer buf, int len)
{
Payload = new byte[len];
buf.ReadBytes(Payload, 0, len);
}
}

class AuthenticationSCRAMServerFinalMessage
{
static readonly NpgsqlLogger Log = NpgsqlLogManager.GetCurrentClassLogger();

internal string ServerSignature { get; }

internal AuthenticationSCRAMServerFinalMessage(byte[] bytes)
{
var data = PGUtil.UTF8Encoding.GetString(bytes);

foreach (var part in data.Split(','))
{
if (part.StartsWith("v="))
ServerSignature = part.Substring(2);
else
Log.Debug("Unknown part in SCRAM server-first message:" + part);
}

if (ServerSignature == null)
throw new NpgsqlException("Server signature not received in SCRAM server-final message");
}
}

#endregion SASL

// TODO: Remove Authentication prefix from everything
enum AuthenticationRequestType
{
AuthenticationOk = 0,
@@ -127,6 +231,9 @@ enum AuthenticationRequestType
AuthenticationSCMCredential = 6,
AuthenticationGSS = 7,
AuthenticationGSSContinue = 8,
AuthenticationSSPI = 9
AuthenticationSSPI = 9,
AuthenticationSASL = 10,
AuthenticationSASLContinue = 11,
AuthenticationSASLFinal = 12
}
}
@@ -28,6 +28,7 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Npgsql.BackendMessages;

namespace Npgsql.FrontendMessages
@@ -129,4 +130,123 @@ internal override async Task Write(WriteBuffer buf, bool async, CancellationToke

public override string ToString() => "[Password]";
}

#region SASL

// TODO: Refactor above password messages into different classes to harmonize, clean up
class SASLInitialResponseMessage : SimpleFrontendMessage
{
const byte Code = (byte)'p';
readonly string _mechanism;
[CanBeNull]
readonly byte[] _initialResponse;

internal SASLInitialResponseMessage(string mechanism, byte[] initialResponse)
{
_mechanism = mechanism;
_initialResponse = initialResponse;
}

internal override int Length =>
1 + 4 +
PGUtil.UTF8Encoding.GetByteCount(_mechanism) + 1 +
4 + _initialResponse?.Length ?? 0;

internal override void WriteFully(WriteBuffer buf)
{
buf.WriteByte(Code);
buf.WriteInt32(Length - 1);

buf.WriteString(_mechanism);
buf.WriteByte(0); // null terminator
if (_initialResponse == null)
buf.WriteInt32(-1);
else
{
buf.WriteInt32(_initialResponse.Length);
buf.WriteBytes(_initialResponse);
}
}
}

class SCRAMClientFinalMessage : SimpleFrontendMessage
{
const byte Code = (byte)'p';

readonly string _messageStr;

const string ClientKey = "Client Key";
const string ServerKey = "Server Key";

internal byte[] ServerSignature { get; }

internal SCRAMClientFinalMessage(string password, string serverNonce, string salt, int serverIteration, string clientNonce)
{
var saltBytes = Convert.FromBase64String(salt);
var saltedPassword = Hi(password.Normalize(NormalizationForm.FormKC), saltBytes, serverIteration);

var clientKey = HMAC(saltedPassword, ClientKey);
var storedKey = SHA256.Create().ComputeHash(clientKey);

var clientFirstMessageBare = "n=*,r=" + clientNonce;
var serverFirstMessage = $"r={serverNonce},s={salt},i={serverIteration}";
var clientFinalMessageWithoutProof = "c=biws,r=" + serverNonce;

var authMessage = $"{clientFirstMessageBare},{serverFirstMessage},{clientFinalMessageWithoutProof}";

var clientSignature = HMAC(storedKey, authMessage);
var clientProofBytes = XOR(clientKey, clientSignature);
var clientProof = Convert.ToBase64String(clientProofBytes);

var serverKey = HMAC(saltedPassword, ServerKey);
var serverSignatureBytes = HMAC(serverKey, authMessage);
ServerSignature = serverSignatureBytes;

_messageStr = $"{clientFinalMessageWithoutProof},p={clientProof}";
}

internal override int Length => 1 + 4 + PGUtil.UTF8Encoding.GetByteCount(_messageStr);

internal override void WriteFully(WriteBuffer buf)
{
buf.WriteByte(Code);
buf.WriteInt32(Length - 1);
buf.WriteString(_messageStr);
}

static byte[] Hi(string str, byte[] salt, int count)
{
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(str)))
{
var salt1 = new byte[salt.Length + 4];
byte[] hi, u1;

Buffer.BlockCopy(salt, 0, salt1, 0, salt.Length);
salt1[salt1.Length - 1] = (byte)1;

hi = u1 = hmac.ComputeHash(salt1);

for (var i = 1; i < count; i++)
{
var u2 = hmac.ComputeHash(u1);
XOR(hi, u2);
u1 = u2;
}

return hi;
}
}

static byte[] XOR(byte[] buffer1, byte[] buffer2)
{
for (var i = 0; i < buffer1.Length; i++)
buffer1[i] ^= buffer2[i];
return buffer1;
}

static byte[] HMAC(byte[] data, string key)
=> new HMACSHA256(data).ComputeHash(Encoding.UTF8.GetBytes(key));
}

#endregion SASL
}
@@ -6,7 +6,7 @@
<Copyright>Copyright 2017 © The Npgsql Development Team</Copyright>
<Company>Npgsql</Company>
<PackageTags>npgsql postgresql postgres ado ado.net database sql</PackageTags>
<VersionPrefix>3.2.6</VersionPrefix>
<VersionPrefix>3.2.7</VersionPrefix>
<TargetFrameworks>net45;net451;netstandard1.3;netstandard2.0</TargetFrameworks>
<WarningsAsErrors>true</WarningsAsErrors>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -413,6 +413,8 @@ public override void Prepare()
}
}

_connectorPreparedOn = connector;

// It's possible the command was already prepared, or that presistent prepared statements were found for
// all statements. Nothing to do here, move along.
if (!needToPrepare)
@@ -456,8 +458,6 @@ public override void Prepare()

connector.ReadExpecting<ReadyForQueryMessage>();
sendTask.GetAwaiter().GetResult();

_connectorPreparedOn = connector;
}
}

Oops, something went wrong.

0 comments on commit f26b14a

Please sign in to comment.