// Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. // // 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 System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Security; using Sog.Properties; namespace MySql.Data.MySqlClient.Authentication { /// /// /// [SuppressUnmanagedCodeSecurity()] internal class MySqlWindowsAuthenticationPlugin : MySqlAuthenticationPlugin { SECURITY_HANDLE outboundCredentials = new SECURITY_HANDLE(0); SECURITY_HANDLE clientContext = new SECURITY_HANDLE(0); SECURITY_INTEGER lifetime = new SECURITY_INTEGER(0); bool continueProcessing; string targetName = null; protected override void CheckConstraints() { string platform = String.Empty; int p = (int)Environment.OSVersion.Platform; if ((p == 4) || (p == 128)) { platform = "Unix"; } else if (Environment.OSVersion.Platform == PlatformID.MacOSX) { platform = "Mac OS/X"; } if (!String.IsNullOrEmpty(platform)) { throw new MySqlException(String.Format(Resources.WinAuthNotSupportOnPlatform, platform)); } base.CheckConstraints(); } public override string GetUsername() { string username = base.GetUsername(); if (String.IsNullOrEmpty(username)) { return "auth_windows"; } return username; } public override string PluginName { get { return "authentication_windows_client"; } } protected override byte[] MoreData(byte[] moreData) { if (moreData == null) { this.AcquireCredentials(); } byte[] clientBlob = null; if (this.continueProcessing) { this.InitializeClient(out clientBlob, moreData, out this.continueProcessing); } if (!this.continueProcessing || clientBlob == null || clientBlob.Length == 0) { FreeCredentialsHandle(ref this.outboundCredentials); DeleteSecurityContext(ref this.clientContext); return null; } return clientBlob; } void InitializeClient(out byte[] clientBlob, byte[] serverBlob, out bool continueProcessing) { clientBlob = null; continueProcessing = true; SecBufferDesc clientBufferDesc = new SecBufferDesc(MAX_TOKEN_SIZE); SECURITY_INTEGER initLifetime = new SECURITY_INTEGER(0); int ss = -1; try { uint ContextAttributes = 0; if (serverBlob == null) { ss = InitializeSecurityContext( ref this.outboundCredentials, IntPtr.Zero, this.targetName, STANDARD_CONTEXT_ATTRIBUTES, 0, SECURITY_NETWORK_DREP, IntPtr.Zero, /* always zero first time around */ 0, out this.clientContext, out clientBufferDesc, out ContextAttributes, out initLifetime); } else { SecBufferDesc serverBufferDesc = new SecBufferDesc(serverBlob); try { ss = InitializeSecurityContext( ref this.outboundCredentials, ref this.clientContext, this.targetName, STANDARD_CONTEXT_ATTRIBUTES, 0, SECURITY_NETWORK_DREP, ref serverBufferDesc, 0, out this.clientContext, out clientBufferDesc, out ContextAttributes, out initLifetime); } finally { serverBufferDesc.Dispose(); } } if ((SEC_I_COMPLETE_NEEDED == ss) || (SEC_I_COMPLETE_AND_CONTINUE == ss)) { CompleteAuthToken(ref this.clientContext, ref clientBufferDesc); } if (ss != SEC_E_OK && ss != SEC_I_CONTINUE_NEEDED && ss != SEC_I_COMPLETE_NEEDED && ss != SEC_I_COMPLETE_AND_CONTINUE) { throw new MySqlException( "InitializeSecurityContext() failed with errorcode " + ss); } clientBlob = clientBufferDesc.GetSecBufferByteArray(); } finally { clientBufferDesc.Dispose(); } continueProcessing = (ss != SEC_E_OK && ss != SEC_I_COMPLETE_NEEDED); } private void AcquireCredentials() { this.continueProcessing = true; int ss = AcquireCredentialsHandle(null, "Negotiate", SECPKG_CRED_OUTBOUND, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, ref this.outboundCredentials, ref this.lifetime); if (ss != SEC_E_OK) { throw new MySqlException("AcquireCredentialsHandle failed with errorcode" + ss); } } #region SSPI Constants and Imports const int SEC_E_OK = 0; const int SEC_I_CONTINUE_NEEDED = 0x90312; const int SEC_I_COMPLETE_NEEDED = 0x1013; const int SEC_I_COMPLETE_AND_CONTINUE = 0x1014; const int SECPKG_CRED_OUTBOUND = 2; const int SECURITY_NETWORK_DREP = 0; const int SECURITY_NATIVE_DREP = 0x10; const int SECPKG_CRED_INBOUND = 1; const int MAX_TOKEN_SIZE = 12288; const int SECPKG_ATTR_SIZES = 0; const int STANDARD_CONTEXT_ATTRIBUTES = 0; [DllImport("secur32", CharSet = CharSet.Unicode)] static extern int AcquireCredentialsHandle( string pszPrincipal, string pszPackage, int fCredentialUse, IntPtr PAuthenticationID, IntPtr pAuthData, int pGetKeyFn, IntPtr pvGetKeyArgument, ref SECURITY_HANDLE phCredential, ref SECURITY_INTEGER ptsExpiry); [DllImport("secur32", CharSet = CharSet.Unicode, SetLastError = true)] static extern int InitializeSecurityContext( ref SECURITY_HANDLE phCredential, IntPtr phContext, string pszTargetName, int fContextReq, int Reserved1, int TargetDataRep, IntPtr pInput, int Reserved2, out SECURITY_HANDLE phNewContext, out SecBufferDesc pOutput, out uint pfContextAttr, out SECURITY_INTEGER ptsExpiry); [DllImport("secur32", CharSet = CharSet.Unicode, SetLastError = true)] static extern int InitializeSecurityContext( ref SECURITY_HANDLE phCredential, ref SECURITY_HANDLE phContext, string pszTargetName, int fContextReq, int Reserved1, int TargetDataRep, ref SecBufferDesc SecBufferDesc, int Reserved2, out SECURITY_HANDLE phNewContext, out SecBufferDesc pOutput, out uint pfContextAttr, out SECURITY_INTEGER ptsExpiry); [DllImport("secur32", CharSet = CharSet.Unicode, SetLastError = true)] static extern int CompleteAuthToken( ref SECURITY_HANDLE phContext, ref SecBufferDesc pToken); [DllImport("secur32.Dll", CharSet = CharSet.Unicode, SetLastError = false)] public static extern int QueryContextAttributes( ref SECURITY_HANDLE phContext, uint ulAttribute, out SecPkgContext_Sizes pContextAttributes); [DllImport("secur32.Dll", CharSet = CharSet.Unicode, SetLastError = false)] public static extern int FreeCredentialsHandle(ref SECURITY_HANDLE pCred); [DllImport("secur32.Dll", CharSet = CharSet.Unicode, SetLastError = false)] public static extern int DeleteSecurityContext(ref SECURITY_HANDLE pCred); #endregion } [StructLayout(LayoutKind.Sequential)] struct SecBufferDesc : IDisposable { public int ulVersion; public int cBuffers; public IntPtr pBuffers; // Point to SecBuffer public SecBufferDesc(int bufferSize) { this.ulVersion = (int)SecBufferType.SECBUFFER_VERSION; this.cBuffers = 1; SecBuffer secBuffer = new SecBuffer(bufferSize); this.pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(secBuffer)); Marshal.StructureToPtr(secBuffer, this.pBuffers, false); } public SecBufferDesc(byte[] secBufferBytes) { this.ulVersion = (int)SecBufferType.SECBUFFER_VERSION; this.cBuffers = 1; SecBuffer thisSecBuffer = new SecBuffer(secBufferBytes); this.pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(thisSecBuffer)); Marshal.StructureToPtr(thisSecBuffer, this.pBuffers, false); } public void Dispose() { if (this.pBuffers != IntPtr.Zero) { Debug.Assert(this.cBuffers == 1); SecBuffer ThisSecBuffer = Marshal.PtrToStructure(this.pBuffers); ThisSecBuffer.Dispose(); Marshal.FreeHGlobal(this.pBuffers); this.pBuffers = IntPtr.Zero; } } public byte[] GetSecBufferByteArray() { byte[] Buffer = null; if (this.pBuffers == IntPtr.Zero) { throw new InvalidOperationException("Object has already been disposed!!!"); } Debug.Assert(this.cBuffers == 1); SecBuffer secBuffer = Marshal.PtrToStructure(this.pBuffers); if (secBuffer.cbBuffer > 0) { Buffer = new byte[secBuffer.cbBuffer]; Marshal.Copy(secBuffer.pvBuffer, Buffer, 0, secBuffer.cbBuffer); } return (Buffer); } } /// /// Defines the type of the security buffer. /// public enum SecBufferType { SECBUFFER_VERSION = 0, SECBUFFER_EMPTY = 0, SECBUFFER_DATA = 1, SECBUFFER_TOKEN = 2 } /// /// Defines a security handle. /// [StructLayout(LayoutKind.Sequential)] public struct SecHandle // =PCtxtHandle { IntPtr dwLower; // ULONG_PTR translates to IntPtr not to uint IntPtr dwUpper; // this is crucial for 64-Bit Platforms } /// /// Describes a buffer allocated by a transport to pass to a security package. /// [StructLayout(LayoutKind.Sequential)] public struct SecBuffer : IDisposable { /// /// Specifies the size, in bytes, of the buffer. /// public int cbBuffer; /// /// Bit flags that indicate the type of the buffer. /// public int BufferType; /// /// Pointer to a buffer. /// public IntPtr pvBuffer; public SecBuffer(int bufferSize) { this.cbBuffer = bufferSize; this.BufferType = (int)SecBufferType.SECBUFFER_TOKEN; this.pvBuffer = Marshal.AllocHGlobal(bufferSize); } public SecBuffer(byte[] secBufferBytes) { this.cbBuffer = secBufferBytes.Length; this.BufferType = (int)SecBufferType.SECBUFFER_TOKEN; this.pvBuffer = Marshal.AllocHGlobal(this.cbBuffer); Marshal.Copy(secBufferBytes, 0, this.pvBuffer, this.cbBuffer); } public SecBuffer(byte[] secBufferBytes, SecBufferType bufferType) { this.cbBuffer = secBufferBytes.Length; this.BufferType = (int)bufferType; this.pvBuffer = Marshal.AllocHGlobal(this.cbBuffer); Marshal.Copy(secBufferBytes, 0, this.pvBuffer, this.cbBuffer); } public void Dispose() { if (this.pvBuffer != IntPtr.Zero) { Marshal.FreeHGlobal(this.pvBuffer); this.pvBuffer = IntPtr.Zero; } } } /// /// Hold a numeric value used in defining other data types. /// [StructLayout(LayoutKind.Sequential)] public struct SECURITY_INTEGER { /// /// Least significant digits. /// public uint LowPart; /// /// Most significant digits. /// public int HighPart; public SECURITY_INTEGER(int dummy) { this.LowPart = 0; this.HighPart = 0; } } ; /// /// Holds a pointer used to define a security handle. /// [StructLayout(LayoutKind.Sequential)] public struct SECURITY_HANDLE { /// /// Least significant digits. /// public IntPtr LowPart; /// /// Most significant digits. /// public IntPtr HighPart; public SECURITY_HANDLE(int dummy) { this.LowPart = this.HighPart = new IntPtr(0); } } ; /// /// Indicates the sizes of important structures used in the message support functions. /// [StructLayout(LayoutKind.Sequential)] public struct SecPkgContext_Sizes { /// /// Specifies the maximum size of the security token used in the authentication changes. /// public uint cbMaxToken; /// /// Specifies the maximum size of the signature created by the MakeSignature function. /// This member must be zero if integrity services are not requested or available. /// public uint cbMaxSignature; /// /// Specifies the preferred integral size of the messages. /// public uint cbBlockSize; /// /// Size of the security trailer to be appended to messages. /// This member should be zero if the relevant services are not requested or available. /// public uint cbSecurityTrailer; } ; }