You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
12 KiB
283 lines
12 KiB
// Copyright (c) 2020, Oracle and/or its affiliates.
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License, version 2.0, as
|
|
// published by the Free Software Foundation.
|
|
//
|
|
// This program is also distributed with certain software (including
|
|
// but not limited to OpenSSL) that is licensed under separate terms,
|
|
// as designated in a particular file or component or in included license
|
|
// documentation. The authors of MySQL hereby grant you an
|
|
// additional permission to link the program and your derivative works
|
|
// with the separately licensed software that they have included with
|
|
// MySQL.
|
|
//
|
|
// Without limiting anything contained in the foregoing, this file,
|
|
// which is part of MySQL Connector/NET, is also subject to the
|
|
// Universal FOSS Exception, version 1.0, a copy of which can be found at
|
|
|
|
// http://oss.oracle.com/licenses/universal-foss-exception.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
// See the GNU General Public License, version 2.0, for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software Foundation, Inc.,
|
|
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
using MySql.Data.Common;
|
|
using System;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Sog.Properties;
|
|
|
|
namespace MySql.Data.MySqlClient.Authentication
|
|
{
|
|
/// <summary>
|
|
/// Allows connections to a user account set with the authentication_ldap_sasl authentication plugin.
|
|
/// </summary>
|
|
internal class MySqlSASLPlugin : MySqlAuthenticationPlugin
|
|
{
|
|
public override string PluginName => "mysql_ldap_sasl";
|
|
string _mechanismName;
|
|
internal static ScramBase scramMechanism;
|
|
internal static GssapiMechanism gssapiMechanism;
|
|
|
|
protected override void SetAuthData(byte[] data)
|
|
{
|
|
this._mechanismName = Encoding.UTF8.GetString(data);
|
|
|
|
switch (this._mechanismName)
|
|
{
|
|
case "SCRAM-SHA-1":
|
|
scramMechanism = new ScramSha1Mechanism(this.GetUsername(), this.Settings.Password, this.Settings.Server);
|
|
break;
|
|
case "SCRAM-SHA-256":
|
|
scramMechanism = new ScramSha256Mechanism(this.GetUsername(), this.Settings.Password, this.Settings.Server);
|
|
break;
|
|
case "GSSAPI":
|
|
if (Platform.IsWindows())
|
|
{
|
|
throw new PlatformNotSupportedException(string.Format(Resources.AuthenticationPluginNotSupported, "GSSAPI/Kerberos"));
|
|
}
|
|
|
|
gssapiMechanism = new GssapiMechanism(this.GetUsername(), this.Settings.Password);
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected override byte[] MoreData(byte[] data)
|
|
{
|
|
if (this._mechanismName == "GSSAPI")
|
|
{
|
|
return gssapiMechanism.Challenge(data);
|
|
}
|
|
else
|
|
{
|
|
return scramMechanism.Challenge(data);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the character is a non-ASCII space.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This list was obtained from http://tools.ietf.org/html/rfc3454#appendix-C.1.2
|
|
/// </remarks>
|
|
/// <returns><c>true</c> if the character is a non-ASCII space; otherwise, <c>false</c>.</returns>
|
|
/// <param name="c">The character.</param>
|
|
internal static bool IsNonAsciiSpace(char c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '\u00A0': // NO-BREAK SPACE
|
|
case '\u1680': // OGHAM SPACE MARK
|
|
case '\u2000': // EN QUAD
|
|
case '\u2001': // EM QUAD
|
|
case '\u2002': // EN SPACE
|
|
case '\u2003': // EM SPACE
|
|
case '\u2004': // THREE-PER-EM SPACE
|
|
case '\u2005': // FOUR-PER-EM SPACE
|
|
case '\u2006': // SIX-PER-EM SPACE
|
|
case '\u2007': // FIGURE SPACE
|
|
case '\u2008': // PUNCTUATION SPACE
|
|
case '\u2009': // THIN SPACE
|
|
case '\u200A': // HAIR SPACE
|
|
case '\u200B': // ZERO WIDTH SPACE
|
|
case '\u202F': // NARROW NO-BREAK SPACE
|
|
case '\u205F': // MEDIUM MATHEMATICAL SPACE
|
|
case '\u3000': // IDEOGRAPHIC SPACE
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the character is commonly mapped to nothing.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This list was obtained from http://tools.ietf.org/html/rfc3454#appendix-B.1
|
|
/// </remarks>
|
|
/// <returns><c>true</c> if the character is commonly mapped to nothing; otherwise, <c>false</c>.</returns>
|
|
/// <param name="c">The character.</param>
|
|
internal static bool IsCommonlyMappedToNothing(char c)
|
|
{
|
|
return c == '\u00AD' || c == '\u034F' || c == '\u1806' || c >= '\u180B' && c <= '\u180D' || c >= '\u200B' && c <= '\u200D'
|
|
|| c == '\u2060' || c >= '\uFE00' && c <= '\uFE0F' || c == '\uFEFF';
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the character is prohibited.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This list was obtained from http://tools.ietf.org/html/rfc3454#appendix-C.3
|
|
/// </remarks>
|
|
/// <returns><c>true</c> if the character is prohibited; otherwise, <c>false</c>.</returns>
|
|
/// <param name="s">The string.</param>
|
|
/// <param name="index">The character index.</param>
|
|
internal static bool IsProhibited(string s, int index)
|
|
{
|
|
int u = char.ConvertToUtf32(s, index);
|
|
|
|
// Non-ASCII control characters: https://tools.ietf.org/html/rfc3454#appendix-C.2.2
|
|
if ((u >= 0x0080 && u <= 0x009F) || u == 0x06DD || u == 0x070F || u == 0x180E || u == 0x200C || u == 0x200D ||
|
|
u == 0x2028 || u == 0x2029 || (u >= 0x2060 && u <= 0x2063) || (u >= 0x206A && u <= 0x206F) || u == 0xFEFF ||
|
|
(u >= 0xFFF9 && u <= 0xFFFC) || (u >= 0x1D173 && u <= 0x1D17A))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Private Use characters: http://tools.ietf.org/html/rfc3454#appendix-C.3
|
|
if ((u >= 0xE000 && u <= 0xF8FF) || (u >= 0xF0000 && u <= 0xFFFFD) || (u >= 0x100000 && u <= 0x10FFFD))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Non-character code points: http://tools.ietf.org/html/rfc3454#appendix-C.4
|
|
if ((u >= 0xFDD0 && u <= 0xFDEF) || (u >= 0xFFFE && u <= 0xFFFF) || (u >= 0x1FFFE && u <= 0x1FFFF) ||
|
|
(u >= 0x2FFFE && u <= 0x2FFFF) || (u >= 0x3FFFE && u <= 0x3FFFF) || (u >= 0x4FFFE && u <= 0x4FFFF) ||
|
|
(u >= 0x5FFFE && u <= 0x5FFFF) || (u >= 0x6FFFE && u <= 0x6FFFF) || (u >= 0x7FFFE && u <= 0x7FFFF) ||
|
|
(u >= 0x8FFFE && u <= 0x8FFFF) || (u >= 0x9FFFE && u <= 0x9FFFF) || (u >= 0xAFFFE && u <= 0xAFFFF) ||
|
|
(u >= 0xBFFFE && u <= 0xBFFFF) || (u >= 0xCFFFE && u <= 0xCFFFF) || (u >= 0xDFFFE && u <= 0xDFFFF) ||
|
|
(u >= 0xEFFFE && u <= 0xEFFFF) || (u >= 0xFFFFE && u <= 0xFFFFF) || (u >= 0x10FFFE && u <= 0x10FFFF))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Surrogate code points: http://tools.ietf.org/html/rfc3454#appendix-C.5
|
|
if (char.IsSurrogate(s, index))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Inappropriate for plain text: http://tools.ietf.org/html/rfc3454#appendix-C.6
|
|
switch (u)
|
|
{
|
|
case 0xFFF9: // INTERLINEAR ANNOTATION ANCHOR
|
|
case 0xFFFA: // INTERLINEAR ANNOTATION SEPARATOR
|
|
case 0xFFFB: // INTERLINEAR ANNOTATION TERMINATOR
|
|
case 0xFFFC: // OBJECT REPLACEMENT CHARACTER
|
|
case 0xFFFD: // REPLACEMENT CHARACTER
|
|
return true;
|
|
}
|
|
|
|
// Inappropriate for canonical representation: http://tools.ietf.org/html/rfc3454#appendix-C.7
|
|
if (u >= 0x2FF0 && u <= 0x2FFB)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Change display properties or are deprecated: http://tools.ietf.org/html/rfc3454#appendix-C.8
|
|
switch (u)
|
|
{
|
|
case 0x0340: // COMBINING GRAVE TONE MARK
|
|
case 0x0341: // COMBINING ACUTE TONE MARK
|
|
case 0x200E: // LEFT-TO-RIGHT MARK
|
|
case 0x200F: // RIGHT-TO-LEFT MARK
|
|
case 0x202A: // LEFT-TO-RIGHT EMBEDDING
|
|
case 0x202B: // RIGHT-TO-LEFT EMBEDDING
|
|
case 0x202C: // POP DIRECTIONAL FORMATTING
|
|
case 0x202D: // LEFT-TO-RIGHT OVERRIDE
|
|
case 0x202E: // RIGHT-TO-LEFT OVERRIDE
|
|
case 0x206A: // INHIBIT SYMMETRIC SWAPPING
|
|
case 0x206B: // ACTIVATE SYMMETRIC SWAPPING
|
|
case 0x206C: // INHIBIT ARABIC FORM SHAPING
|
|
case 0x206D: // ACTIVATE ARABIC FORM SHAPING
|
|
case 0x206E: // NATIONAL DIGIT SHAPES
|
|
case 0x206F: // NOMINAL DIGIT SHAPES
|
|
return true;
|
|
}
|
|
|
|
// Tagging characters: http://tools.ietf.org/html/rfc3454#appendix-C.9
|
|
if (u == 0xE0001 || (u >= 0xE0020 && u <= 0xE007F))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Prepares the user name or password string.
|
|
/// </summary>
|
|
/// <param name="s">The string to prepare.</param>
|
|
/// <returns>The prepared string.</returns>
|
|
internal static string SaslPrep(string s)
|
|
{
|
|
if (s == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(s));
|
|
}
|
|
|
|
if (s.Length == 0)
|
|
{
|
|
return s;
|
|
}
|
|
|
|
var builder = new StringBuilder(s.Length);
|
|
for (int i = 0; i < s.Length; i++)
|
|
{
|
|
if (IsNonAsciiSpace(s[i]))
|
|
{
|
|
// non-ASII space characters [StringPrep, C.1.2] that can be
|
|
// mapped to SPACE (U+0020).
|
|
builder.Append(' ');
|
|
}
|
|
else if (IsCommonlyMappedToNothing(s[i]))
|
|
{
|
|
// the "commonly mapped to nothing" characters [StringPrep, B.1]
|
|
// that can be mapped to nothing.
|
|
}
|
|
else if (char.IsControl(s[i]))
|
|
{
|
|
throw new ArgumentException("Control characters are prohibited.", nameof(s));
|
|
}
|
|
else if (IsProhibited(s, i))
|
|
{
|
|
throw new ArgumentException("One or more characters in the string are prohibited.", nameof(s));
|
|
}
|
|
else
|
|
{
|
|
builder.Append(s[i]);
|
|
}
|
|
}
|
|
|
|
return builder.ToString().Normalize(NormalizationForm.FormKC);
|
|
}
|
|
|
|
internal static string GetRandomBytes(int n)
|
|
{
|
|
var bytes = new byte[n];
|
|
|
|
using (var rng = RandomNumberGenerator.Create())
|
|
{
|
|
rng.GetBytes(bytes);
|
|
}
|
|
|
|
return Convert.ToBase64String(bytes);
|
|
}
|
|
}
|
|
}
|