< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 84
Coverable lines: 84
Total lines: 1094
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 16
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor()100%110%
AcquireDefaultCredential(...)0%440%
AcquireCredentialsHandle(...)0%220%
AcquireCredentialsHandle(...)0%440%
AcquireCredentialsHandle(...)0%440%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Diagnostics;
 5using System.Runtime.InteropServices;
 6using System.Security.Authentication.ExtendedProtection;
 7using System.Security.Cryptography.X509Certificates;
 8using Microsoft.Win32.SafeHandles;
 9
 10namespace System.Net.Security
 11{
 12    //
 13    // Used when working with SSPI APIs, like SafeSspiAuthDataHandle(). Holds the pointer to the auth data blob.
 14    //
 15#if DEBUG
 16    internal sealed class SafeSspiAuthDataHandle : DebugSafeHandle
 17    {
 18#else
 19    internal sealed class SafeSspiAuthDataHandle : SafeHandleZeroOrMinusOneIsInvalid
 20    {
 21#endif
 22        public SafeSspiAuthDataHandle() : base(true)
 23        {
 24        }
 25
 26        protected override bool ReleaseHandle()
 27        {
 28            return Interop.SspiCli.SspiFreeAuthIdentity(handle) == Interop.SECURITY_STATUS.OK;
 29        }
 30    }
 31
 32    //
 33    //  A set of Safe Handles that depend on native FreeContextBuffer finalizer.
 34    //
 35#if DEBUG
 36    internal abstract class SafeFreeContextBuffer : DebugSafeHandle
 37    {
 38#else
 39    internal abstract class SafeFreeContextBuffer : SafeHandleZeroOrMinusOneIsInvalid
 40    {
 41#endif
 42        protected SafeFreeContextBuffer() : base(true) { }
 43
 44        // This must be ONLY called from this file.
 45        internal void Set(IntPtr value)
 46        {
 47            this.handle = value;
 48        }
 49
 50        internal static int EnumeratePackages(out int pkgnum, out SafeFreeContextBuffer pkgArray)
 51        {
 52            int res = Interop.SspiCli.EnumerateSecurityPackagesW(out pkgnum, out SafeFreeContextBuffer_SECURITY? pkgArra
 53            pkgArray = pkgArray_SECURITY;
 54
 55            if (res != 0)
 56            {
 57                pkgArray?.SetHandleAsInvalid();
 58            }
 59
 60            return res;
 61        }
 62
 63        internal static SafeFreeContextBuffer CreateEmptyHandle()
 64        {
 65            return new SafeFreeContextBuffer_SECURITY();
 66        }
 67
 68        public static unsafe int QueryContextAttributes(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute co
 69        {
 70            bool mustRelease = false;
 71            try
 72            {
 73                phContext.DangerousAddRef(ref mustRelease);
 74                return Interop.SspiCli.QueryContextAttributesW(ref phContext._handle, contextAttribute, handle);
 75            }
 76            finally
 77            {
 78                if (mustRelease)
 79                {
 80                    phContext.DangerousRelease();
 81                }
 82            }
 83        }
 84
 85        //
 86        // After PInvoke call the method will fix the refHandle.handle with the returned value.
 87        // The caller is responsible for creating a correct SafeHandle template or null can be passed if no handle is re
 88        //
 89        // This method switches between three non-interruptible helper methods.  (This method can't be both non-interrup
 90        // reference imports from all three DLLs - doing so would cause all three DLLs to try to be bound to.)
 91        //
 92        public static unsafe int QueryContextAttributes(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute co
 93        {
 94            int status = (int)Interop.SECURITY_STATUS.InvalidHandle;
 95
 96            bool mustRelease = false;
 97            try
 98            {
 99                phContext.DangerousAddRef(ref mustRelease);
 100                status = Interop.SspiCli.QueryContextAttributesW(ref phContext._handle, contextAttribute, buffer);
 101            }
 102            finally
 103            {
 104                if (mustRelease)
 105                {
 106                    phContext.DangerousRelease();
 107                }
 108            }
 109
 110            if (status == 0 && refHandle != null)
 111            {
 112                if (refHandle is SafeFreeContextBuffer)
 113                {
 114                    ((SafeFreeContextBuffer)refHandle).Set(*(IntPtr*)buffer);
 115                }
 116                else
 117                {
 118                    Debug.Assert(false);
 119                }
 120            }
 121
 122            if (status != 0)
 123            {
 124                refHandle?.SetHandleAsInvalid();
 125            }
 126
 127            return status;
 128        }
 129
 130        public static int SetContextAttributes(
 131            SafeDeleteContext phContext,
 132            Interop.SspiCli.ContextAttribute contextAttribute, byte[] buffer)
 133        {
 134            bool mustRelease = false;
 135            try
 136            {
 137                phContext.DangerousAddRef(ref mustRelease);
 138                return Interop.SspiCli.SetContextAttributesW(ref phContext._handle, contextAttribute, buffer, buffer.Len
 139            }
 140            finally
 141            {
 142                if (mustRelease)
 143                {
 144                    phContext.DangerousRelease();
 145                }
 146            }
 147        }
 148    }
 149
 150    internal sealed class SafeFreeContextBuffer_SECURITY : SafeFreeContextBuffer
 151    {
 152        public SafeFreeContextBuffer_SECURITY() : base() { }
 153
 154        protected override bool ReleaseHandle()
 155        {
 156            return Interop.SspiCli.FreeContextBuffer(handle) == 0;
 157        }
 158    }
 159
 160    //
 161    // Implementation of handles required CertFreeCertificateContext
 162    //
 163#if DEBUG
 164    internal sealed class SafeFreeCertContext : DebugSafeHandle
 165    {
 166#else
 167    internal sealed class SafeFreeCertContext : SafeHandleZeroOrMinusOneIsInvalid
 168    {
 169#endif
 170
 171        public SafeFreeCertContext() : base(true) { }
 172
 173        // This must be ONLY called from this file.
 174        internal void Set(IntPtr value)
 175        {
 176            this.handle = value;
 177        }
 178
 179        protected override bool ReleaseHandle()
 180        {
 181            Interop.Crypt32.CertFreeCertificateContext(handle);
 182            return true;
 183        }
 184    }
 185
 186    //
 187    // Implementation of handles dependable on FreeCredentialsHandle
 188    //
 189#if DEBUG
 190    internal abstract class SafeFreeCredentials : DebugSafeHandle
 191    {
 192#else
 193    internal abstract class SafeFreeCredentials : SafeHandle
 194    {
 195#endif
 196
 197        internal DateTime _expiry;
 198        internal Interop.SspiCli.CredHandle _handle;    //should be always used as by ref in PInvokes parameters
 199
 0200        protected SafeFreeCredentials() : base(IntPtr.Zero, true)
 0201        {
 0202            _handle = default;
 0203            _expiry = DateTime.MaxValue;
 0204        }
 205
 206        public override bool IsInvalid
 207        {
 0208            get { return IsClosed || _handle.IsZero; }
 209        }
 210
 0211        public DateTime Expiry => _expiry;
 212
 213        public static unsafe int AcquireDefaultCredential(
 214            string package,
 215            Interop.SspiCli.CredentialUse intent,
 216            out SafeFreeCredentials outCredential)
 0217        {
 0218            int errorCode = -1;
 219            long timeStamp;
 220
 0221            outCredential = new SafeFreeCredential_SECURITY();
 222
 0223            errorCode = Interop.SspiCli.AcquireCredentialsHandleW(
 0224                            null,
 0225                            package,
 0226                            (int)intent,
 0227                            null,
 0228                            IntPtr.Zero,
 0229                            null,
 0230                            null,
 0231                            ref outCredential._handle,
 0232                            out timeStamp);
 233
 0234            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"{nameof(Interop.SspiCli.AcquireCredentialsHa
 235
 0236            if (errorCode != 0)
 0237            {
 0238                outCredential.SetHandleAsInvalid();
 0239            }
 240
 0241            return errorCode;
 0242        }
 243
 244        public static unsafe int AcquireCredentialsHandle(
 245            string package,
 246            Interop.SspiCli.CredentialUse intent,
 247            ref SafeSspiAuthDataHandle authdata,
 248            out SafeFreeCredentials outCredential)
 0249        {
 0250            outCredential = new SafeFreeCredential_SECURITY();
 0251            int errorCode = Interop.SspiCli.AcquireCredentialsHandleW(
 0252                            null,
 0253                            package,
 0254                            (int)intent,
 0255                            null,
 0256                            authdata,
 0257                            null,
 0258                            null,
 0259                            ref outCredential._handle,
 0260                            out _);
 261
 0262            if (errorCode != 0)
 0263            {
 0264                outCredential.SetHandleAsInvalid();
 0265            }
 266
 0267            return errorCode;
 0268        }
 269
 270        public static unsafe int AcquireCredentialsHandle(
 271            string package,
 272            Interop.SspiCli.CredentialUse intent,
 273            Interop.SspiCli.SCHANNEL_CRED* authdata,
 274            out SafeFreeCredentials outCredential)
 0275        {
 0276            int errorCode = -1;
 277
 0278            outCredential = new SafeFreeCredential_SECURITY();
 279
 0280            errorCode = Interop.SspiCli.AcquireCredentialsHandleW(
 0281                                null,
 0282                                package,
 0283                                (int)intent,
 0284                                null,
 0285                                authdata,
 0286                                null,
 0287                                null,
 0288                                ref outCredential._handle,
 0289                                out _);
 290
 0291            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"{nameof(Interop.SspiCli.AcquireCredentialsHa
 292
 0293            if (errorCode != 0)
 0294            {
 0295                outCredential.SetHandleAsInvalid();
 0296            }
 297
 0298            return errorCode;
 0299        }
 300
 301        public static unsafe int AcquireCredentialsHandle(
 302            string package,
 303            Interop.SspiCli.CredentialUse intent,
 304            Interop.SspiCli.SCH_CREDENTIALS* authdata,
 305            out SafeFreeCredentials outCredential)
 0306        {
 307            long timeStamp;
 308
 0309            outCredential = new SafeFreeCredential_SECURITY();
 310
 0311            int errorCode = Interop.SspiCli.AcquireCredentialsHandleW(
 0312                                null,
 0313                                package,
 0314                                (int)intent,
 0315                                null,
 0316                                authdata,
 0317                                null,
 0318                                null,
 0319                                ref outCredential._handle,
 0320                                out timeStamp);
 321
 0322            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"{nameof(Interop.SspiCli.AcquireCredentialsHa
 323
 0324            if (errorCode != 0)
 0325            {
 0326                outCredential.SetHandleAsInvalid();
 0327            }
 328
 0329            return errorCode;
 0330        }
 331
 332    }
 333
 334    internal sealed class SafeFreeCredential_SECURITY : SafeFreeCredentials
 335    {
 336#pragma warning disable 0649
 337        // This is used only by SslStream but it is included elsewhere
 338        public bool HasLocalCertificate;
 339#pragma warning restore 0649
 340        public SafeFreeCredential_SECURITY() : base() { }
 341
 342        protected override bool ReleaseHandle()
 343        {
 344            return Interop.SspiCli.FreeCredentialsHandle(ref _handle) == 0;
 345        }
 346    }
 347
 348    //
 349    // Implementation of handles that are dependent on DeleteSecurityContext
 350    //
 351#if DEBUG
 352    internal abstract partial class SafeDeleteContext : DebugSafeHandle
 353    {
 354#else
 355    internal abstract partial class SafeDeleteContext : SafeHandle
 356    {
 357#endif
 358        protected SafeFreeCredentials? _EffectiveCredential;
 359
 360        //-------------------------------------------------------------------
 361        internal static unsafe int InitializeSecurityContext(
 362            ref SafeFreeCredentials? inCredentials,
 363            ref SafeDeleteSslContext? refContext,
 364            string? targetName,
 365            Interop.SspiCli.ContextFlags inFlags,
 366            Interop.SspiCli.Endianness endianness,
 367            ref InputSecurityBuffers inSecBuffers,
 368            ref ProtocolToken outToken,
 369            ref Interop.SspiCli.ContextFlags outFlags)
 370        {
 371            ArgumentNullException.ThrowIfNull(inCredentials);
 372
 373            Debug.Assert(inSecBuffers.Count <= 3);
 374            Interop.SspiCli.SecBufferDesc inSecurityBufferDescriptor = new Interop.SspiCli.SecBufferDesc(inSecBuffers.Co
 375            Interop.SspiCli.SecBufferDesc outSecurityBufferDescriptor = new Interop.SspiCli.SecBufferDesc(1);
 376
 377            // Actually, this is returned in outFlags.
 378            bool isSspiAllocated = (inFlags & Interop.SspiCli.ContextFlags.AllocateMemory) != 0 ? true : false;
 379
 380            int errorCode = -1;
 381
 382            bool isContextAbsent = true;
 383            if (refContext != null)
 384            {
 385                isContextAbsent = refContext._handle.IsZero;
 386            }
 387
 388            // Optional output buffer that may need to be freed.
 389            IntPtr outoutBuffer = IntPtr.Zero;
 390            try
 391            {
 392                Span<Interop.SspiCli.SecBuffer> inUnmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[3];
 393
 394                fixed (void* inUnmanagedBufferPtr = inUnmanagedBuffer)
 395                fixed (void* pinnedToken0 = inSecBuffers._item0.Token)
 396                fixed (void* pinnedToken1 = inSecBuffers._item1.Token)
 397                fixed (void* pinnedToken2 = inSecBuffers._item2.Token)
 398                {
 399                    // Fix Descriptor pointer that points to unmanaged SecurityBuffers.
 400                    inSecurityBufferDescriptor.pBuffers = inUnmanagedBufferPtr;
 401                    // Updated pvBuffer with pinned address. UnmanagedToken takes precedence.
 402                    if (inSecBuffers.Count > 2)
 403                    {
 404                        inUnmanagedBuffer[2].BufferType = inSecBuffers._item2.Type;
 405                        if (inSecBuffers._item2.UnmanagedToken != null)
 406                        {
 407                            Debug.Assert(inSecBuffers._item2.Type == SecurityBufferType.SECBUFFER_CHANNEL_BINDINGS);
 408                            inUnmanagedBuffer[2].pvBuffer = (IntPtr)inSecBuffers._item2.UnmanagedToken.DangerousGetHandl
 409                            inUnmanagedBuffer[2].cbBuffer = ((ChannelBinding)inSecBuffers._item2.UnmanagedToken).Size;
 410                        }
 411                        else
 412                        {
 413                            inUnmanagedBuffer[2].cbBuffer = inSecBuffers._item2.Token.Length;
 414                            inUnmanagedBuffer[2].pvBuffer = (IntPtr)pinnedToken2;
 415                        }
 416
 417                    }
 418
 419                    if (inSecBuffers.Count > 1)
 420                    {
 421                        inUnmanagedBuffer[1].BufferType = inSecBuffers._item1.Type;
 422                        if (inSecBuffers._item1.UnmanagedToken != null)
 423                        {
 424                            Debug.Assert(inSecBuffers._item1.Type == SecurityBufferType.SECBUFFER_CHANNEL_BINDINGS);
 425                            inUnmanagedBuffer[1].pvBuffer = (IntPtr)inSecBuffers._item1.UnmanagedToken.DangerousGetHandl
 426                            inUnmanagedBuffer[1].cbBuffer = ((ChannelBinding)inSecBuffers._item1.UnmanagedToken).Size;
 427                        }
 428                        else
 429                        {
 430                            inUnmanagedBuffer[1].cbBuffer = inSecBuffers._item1.Token.Length;
 431                            inUnmanagedBuffer[1].pvBuffer = (IntPtr)pinnedToken1;
 432                        }
 433                    }
 434
 435                    if (inSecBuffers.Count > 0)
 436                    {
 437                        inUnmanagedBuffer[0].BufferType = inSecBuffers._item0.Type;
 438                        if (inSecBuffers._item0.UnmanagedToken != null)
 439                        {
 440                            Debug.Assert(inSecBuffers._item0.Type == SecurityBufferType.SECBUFFER_CHANNEL_BINDINGS);
 441                            inUnmanagedBuffer[0].pvBuffer = (IntPtr)inSecBuffers._item0.UnmanagedToken.DangerousGetHandl
 442                            inUnmanagedBuffer[0].cbBuffer = ((ChannelBinding)inSecBuffers._item0.UnmanagedToken).Size;
 443                        }
 444                        else
 445                        {
 446                            inUnmanagedBuffer[0].cbBuffer = inSecBuffers._item0.Token.Length;
 447                            inUnmanagedBuffer[0].pvBuffer = (IntPtr)pinnedToken0;
 448                        }
 449                    }
 450
 451                    fixed (byte* pinnedOutBytes = outToken.Payload)
 452                    {
 453                        // Fix Descriptor pointer that points to unmanaged SecurityBuffers.
 454                        Interop.SspiCli.SecBuffer outUnmanagedBuffer = default;
 455                        outSecurityBufferDescriptor.pBuffers = &outUnmanagedBuffer;
 456                        outUnmanagedBuffer.cbBuffer = outToken.Size;
 457                        outUnmanagedBuffer.BufferType = SecurityBufferType.SECBUFFER_TOKEN;
 458                        outUnmanagedBuffer.pvBuffer = outToken.Payload == null || outToken.Size == 0 ?
 459                            IntPtr.Zero :
 460                            (IntPtr)(pinnedOutBytes);
 461
 462                        if (refContext == null || refContext.IsInvalid)
 463                        {
 464                            // Previous versions unconditionally built a new "refContext" here, but would pass
 465                            // incorrect arguments to InitializeSecurityContextW in cases where an "contextHandle" was
 466                            // already present and non-zero.
 467                            if (isContextAbsent)
 468                            {
 469                                refContext?.Dispose();
 470                                refContext = new SafeDeleteSslContext();
 471                            }
 472                        }
 473
 474                        fixed (char* namePtr = targetName)
 475                        {
 476                            errorCode = MustRunInitializeSecurityContext(
 477                                            ref inCredentials,
 478                                            isContextAbsent,
 479                                            (byte*)namePtr,
 480                                            inFlags,
 481                                            endianness,
 482                                            &inSecurityBufferDescriptor,
 483                                            refContext!,
 484                                            ref outSecurityBufferDescriptor,
 485                                            ref outFlags,
 486                                            null);
 487
 488                            if (isSspiAllocated)
 489                            {
 490                                outoutBuffer = outUnmanagedBuffer.pvBuffer;
 491                            }
 492
 493                            // Get unmanaged buffer with index 0 as the only one passed into PInvoke.
 494                            if (isSspiAllocated)
 495                            {
 496                                if (outUnmanagedBuffer.cbBuffer > 0)
 497                                {
 498                                    outToken.EnsureAvailableSpace(outUnmanagedBuffer.cbBuffer);
 499                                    new Span<byte>((byte*)outUnmanagedBuffer.pvBuffer, outUnmanagedBuffer.cbBuffer).Copy
 500                                }
 501                            }
 502                            outToken.Size = outUnmanagedBuffer.cbBuffer;
 503
 504                            // In some cases schannel may not process all the given data.
 505                            // and it will return them back as SECBUFFER_EXTRA, expecting caller to
 506                            // feed them in again. Propagate this information back up.
 507                            if (inSecBuffers.Count > 1 && inUnmanagedBuffer[1].BufferType == SecurityBufferType.SECBUFFE
 508                            {
 509                                inSecBuffers._item1.Type = inUnmanagedBuffer[1].BufferType;
 510
 511                                // since SecurityBuffer type does not have separate Length field,
 512                                // we point to the unused portion of the input buffer.
 513                                Debug.Assert(inSecBuffers._item0.Token.Length > inUnmanagedBuffer[1].cbBuffer);
 514                                inSecBuffers._item1.Token = inSecBuffers._item0.Token.Slice(inSecBuffers._item0.Token.Le
 515                            }
 516                        }
 517                    }
 518                }
 519            }
 520            finally
 521            {
 522                if (outoutBuffer != IntPtr.Zero)
 523                {
 524                    Interop.SspiCli.FreeContextBuffer(outoutBuffer);
 525                }
 526            }
 527
 528            return errorCode;
 529        }
 530
 531        //
 532        // After PInvoke call the method will fix the handleTemplate.handle with the returned value.
 533        // The caller is responsible for creating a correct SafeFreeContextBuffer_XXX flavor or null can be passed if no
 534        //
 535        private static unsafe int MustRunInitializeSecurityContext(
 536            ref SafeFreeCredentials inCredentials,
 537            bool isContextAbsent,
 538            byte* targetName,
 539            Interop.SspiCli.ContextFlags inFlags,
 540            Interop.SspiCli.Endianness endianness,
 541            Interop.SspiCli.SecBufferDesc* inputBuffer,
 542            SafeDeleteContext outContext,
 543            ref Interop.SspiCli.SecBufferDesc outputBuffer,
 544            ref Interop.SspiCli.ContextFlags attributes,
 545            SafeFreeContextBuffer? handleTemplate)
 546        {
 547            int errorCode = (int)Interop.SECURITY_STATUS.InvalidHandle;
 548
 549            bool mustReleaseCredentials = false;
 550            bool mustReleaseOutContext = false;
 551            try
 552            {
 553                inCredentials.DangerousAddRef(ref mustReleaseCredentials);
 554                outContext.DangerousAddRef(ref mustReleaseOutContext);
 555
 556                Interop.SspiCli.CredHandle credentialHandle = inCredentials._handle;
 557
 558                long timeStamp;
 559
 560                // Now that "outContext" (or "refContext" by the caller) references an actual handle (and cannot
 561                // be closed until it is released below), point "inContextPtr" to its embedded handle (or
 562                // null if the embedded handle has not yet been initialized).
 563                Interop.SspiCli.CredHandle contextHandle = outContext._handle;
 564                void* inContextPtr = contextHandle.IsZero ? null : &contextHandle;
 565
 566                // The "isContextAbsent" supplied by the caller is generally correct but was computed without proper
 567                // synchronization. Rewrite the indicator now that the final "inContext" is known, update if necessary.
 568                isContextAbsent = (inContextPtr == null);
 569
 570                errorCode = Interop.SspiCli.InitializeSecurityContextW(
 571                                ref credentialHandle,
 572                                inContextPtr,
 573                                targetName,
 574                                inFlags,
 575                                0,
 576                                endianness,
 577                                inputBuffer,
 578                                0,
 579                                ref outContext._handle,
 580                                ref outputBuffer,
 581                                ref attributes,
 582                                out timeStamp);
 583            }
 584            finally
 585            {
 586                //
 587                // When a credential handle is first associated with the context we keep credential
 588                // ref count bumped up to ensure ordered finalization.
 589                // If the credential handle has been changed we de-ref the old one and associate the
 590                //  context with the new cred handle but only if the call was successful.
 591                if (outContext._EffectiveCredential != inCredentials && (errorCode & 0x80000000) == 0)
 592                {
 593                    // Disassociate the previous credential handle
 594                    outContext._EffectiveCredential?.DangerousRelease();
 595                    outContext._EffectiveCredential = inCredentials;
 596                }
 597                else if (mustReleaseCredentials)
 598                {
 599                    inCredentials.DangerousRelease();
 600                }
 601
 602                if (mustReleaseOutContext)
 603                {
 604                    outContext.DangerousRelease();
 605                }
 606            }
 607
 608            // The idea is that SSPI has allocated a block and filled up outUnmanagedBuffer+8 slot with the pointer.
 609            if (handleTemplate != null)
 610            {
 611                //ATTN: on 64 BIT that is still +8 cause of 2* c++ unsigned long == 8 bytes
 612                handleTemplate.Set(((Interop.SspiCli.SecBuffer*)outputBuffer.pBuffers)->pvBuffer);
 613                if (handleTemplate.IsInvalid)
 614                {
 615                    handleTemplate.SetHandleAsInvalid();
 616                }
 617            }
 618
 619            if (isContextAbsent && (errorCode & 0x80000000) != 0)
 620            {
 621                // an error on the first call, need to set the out handle to invalid value
 622                outContext._handle.SetToInvalid();
 623            }
 624
 625            return errorCode;
 626        }
 627
 628        //-------------------------------------------------------------------
 629        internal static unsafe int AcceptSecurityContext(
 630            ref SafeFreeCredentials? inCredentials,
 631            ref SafeDeleteSslContext? refContext,
 632            Interop.SspiCli.ContextFlags inFlags,
 633            Interop.SspiCli.Endianness endianness,
 634            ref InputSecurityBuffers inSecBuffers,
 635            ref ProtocolToken outToken,
 636            ref Interop.SspiCli.ContextFlags outFlags)
 637        {
 638            ArgumentNullException.ThrowIfNull(inCredentials);
 639
 640            Debug.Assert(inSecBuffers.Count <= 3);
 641            Interop.SspiCli.SecBufferDesc inSecurityBufferDescriptor = new Interop.SspiCli.SecBufferDesc(inSecBuffers.Co
 642            Interop.SspiCli.SecBufferDesc outSecurityBufferDescriptor = new Interop.SspiCli.SecBufferDesc(count: 2);
 643
 644            // Actually, this is returned in outFlags.
 645            bool isSspiAllocated = (inFlags & Interop.SspiCli.ContextFlags.AllocateMemory) != 0 ? true : false;
 646
 647            int errorCode = -1;
 648
 649            bool isContextAbsent = true;
 650            if (refContext != null)
 651            {
 652                isContextAbsent = refContext._handle.IsZero;
 653            }
 654
 655            Span<Interop.SspiCli.SecBuffer> outUnmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[2];
 656            outUnmanagedBuffer[1].pvBuffer = IntPtr.Zero;
 657            try
 658            {
 659                // Allocate always maximum to allow better code optimization.
 660                Span<Interop.SspiCli.SecBuffer> inUnmanagedBuffer = stackalloc Interop.SspiCli.SecBuffer[3];
 661
 662                fixed (void* inUnmanagedBufferPtr = inUnmanagedBuffer)
 663                fixed (void* outUnmanagedBufferPtr = outUnmanagedBuffer)
 664                fixed (void* pinnedToken0 = inSecBuffers._item0.Token)
 665                fixed (void* pinnedToken1 = inSecBuffers._item1.Token)
 666                fixed (void* pinnedToken2 = inSecBuffers._item2.Token)
 667                {
 668                    inSecurityBufferDescriptor.pBuffers = inUnmanagedBufferPtr;
 669                    // Updated pvBuffer with pinned address. UnmanagedToken takes precedence.
 670                    if (inSecBuffers.Count > 2)
 671                    {
 672                        inUnmanagedBuffer[2].BufferType = inSecBuffers._item2.Type;
 673                        if (inSecBuffers._item2.UnmanagedToken != null)
 674                        {
 675                            Debug.Assert(inSecBuffers._item2.Type == SecurityBufferType.SECBUFFER_CHANNEL_BINDINGS);
 676                            inUnmanagedBuffer[2].pvBuffer = (IntPtr)inSecBuffers._item2.UnmanagedToken.DangerousGetHandl
 677                            inUnmanagedBuffer[2].cbBuffer = ((ChannelBinding)inSecBuffers._item2.UnmanagedToken).Size;
 678                        }
 679                        else
 680                        {
 681                            inUnmanagedBuffer[2].cbBuffer = inSecBuffers._item2.Token.Length;
 682                            inUnmanagedBuffer[2].pvBuffer = (IntPtr)pinnedToken2;
 683                        }
 684
 685                    }
 686
 687                    if (inSecBuffers.Count > 1)
 688                    {
 689                        inUnmanagedBuffer[1].BufferType = inSecBuffers._item1.Type;
 690                        if (inSecBuffers._item1.UnmanagedToken != null)
 691                        {
 692                            Debug.Assert(inSecBuffers._item1.Type == SecurityBufferType.SECBUFFER_CHANNEL_BINDINGS);
 693                            inUnmanagedBuffer[1].pvBuffer = (IntPtr)inSecBuffers._item1.UnmanagedToken.DangerousGetHandl
 694                            inUnmanagedBuffer[1].cbBuffer = ((ChannelBinding)inSecBuffers._item1.UnmanagedToken).Size;
 695                        }
 696                        else
 697                        {
 698                            inUnmanagedBuffer[1].cbBuffer = inSecBuffers._item1.Token.Length;
 699                            inUnmanagedBuffer[1].pvBuffer = (IntPtr)pinnedToken1;
 700                        }
 701                    }
 702
 703                    if (inSecBuffers.Count > 0)
 704                    {
 705                        inUnmanagedBuffer[0].BufferType = inSecBuffers._item0.Type;
 706                        if (inSecBuffers._item0.UnmanagedToken != null)
 707                        {
 708                            Debug.Assert(inSecBuffers._item0.Type == SecurityBufferType.SECBUFFER_CHANNEL_BINDINGS);
 709                            inUnmanagedBuffer[0].pvBuffer = (IntPtr)inSecBuffers._item0.UnmanagedToken.DangerousGetHandl
 710                            inUnmanagedBuffer[0].cbBuffer = ((ChannelBinding)inSecBuffers._item0.UnmanagedToken).Size;
 711                        }
 712                        else
 713                        {
 714                            inUnmanagedBuffer[0].cbBuffer = inSecBuffers._item0.Token.Length;
 715                            inUnmanagedBuffer[0].pvBuffer = (IntPtr)pinnedToken0;
 716                        }
 717                    }
 718
 719                    fixed (byte* pinnedOutBytes = outToken.Payload)
 720                    {
 721                        // Fix Descriptor pointer that points to unmanaged SecurityBuffers.
 722                        outSecurityBufferDescriptor.pBuffers = outUnmanagedBufferPtr;
 723
 724                        // Copy the SecurityBuffer content into unmanaged place holder.
 725                        outUnmanagedBuffer[0].cbBuffer = outToken.Size;
 726                        outUnmanagedBuffer[0].BufferType = SecurityBufferType.SECBUFFER_TOKEN;
 727                        outUnmanagedBuffer[0].pvBuffer = outToken.Payload == null || outToken.Payload.Length == 0 ?
 728                            IntPtr.Zero :
 729                            (IntPtr)(pinnedOutBytes);
 730
 731                        outUnmanagedBuffer[1].cbBuffer = 0;
 732                        outUnmanagedBuffer[1].BufferType = SecurityBufferType.SECBUFFER_ALERT;
 733
 734                        if (refContext == null || refContext.IsInvalid)
 735                        {
 736                            // Previous versions unconditionally built a new "refContext" here, but would pass
 737                            // incorrect arguments to AcceptSecurityContext in cases where an "contextHandle" was
 738                            // already present and non-zero.
 739                            if (isContextAbsent)
 740                                refContext = new SafeDeleteSslContext();
 741                        }
 742
 743                        errorCode = MustRunAcceptSecurityContext_SECURITY(
 744                                        ref inCredentials,
 745                                        isContextAbsent,
 746                                        &inSecurityBufferDescriptor,
 747                                        inFlags,
 748                                        endianness,
 749                                        refContext!,
 750                                        ref outSecurityBufferDescriptor,
 751                                        ref outFlags,
 752                                        null);
 753
 754                        // No data written out but there is Alert
 755                        int index = outUnmanagedBuffer[0].cbBuffer == 0 && outUnmanagedBuffer[1].cbBuffer > 0 ? 1 : 0;
 756
 757                        int length = outUnmanagedBuffer[index].cbBuffer;
 758                        if (isSspiAllocated && length > 0)
 759                        {
 760                            outToken.EnsureAvailableSpace(length);
 761                            new Span<byte>((byte*)outUnmanagedBuffer[index].pvBuffer, length).CopyTo(outToken.AvailableS
 762                        }
 763                        outToken.Size = length;
 764
 765                        // In some cases schannel may not process all the given data.
 766                        // and it will return them back as SECBUFFER_EXTRA, expecting caller to
 767                        // feed them in again. Propagate this information back up.
 768                        if (inSecBuffers.Count > 1 && inUnmanagedBuffer[1].BufferType == SecurityBufferType.SECBUFFER_EX
 769                        {
 770                            inSecBuffers._item1.Type = inUnmanagedBuffer[1].BufferType;
 771
 772                            // since SecurityBuffer type does not have separate Length field,
 773                            // we point to the unused portion of the input buffer.
 774                            Debug.Assert(inSecBuffers._item0.Token.Length > inUnmanagedBuffer[1].cbBuffer);
 775                            inSecBuffers._item1.Token = inSecBuffers._item0.Token.Slice(inSecBuffers._item0.Token.Length
 776                        }
 777                    }
 778                }
 779            }
 780            finally
 781            {
 782                if (isSspiAllocated && outUnmanagedBuffer[0].pvBuffer != IntPtr.Zero)
 783                {
 784                    Interop.SspiCli.FreeContextBuffer(outUnmanagedBuffer[0].pvBuffer);
 785                }
 786
 787                if (outUnmanagedBuffer[1].pvBuffer != IntPtr.Zero)
 788                {
 789                    Interop.SspiCli.FreeContextBuffer(outUnmanagedBuffer[1].pvBuffer);
 790                }
 791            }
 792
 793            return errorCode;
 794        }
 795
 796        //
 797        // After PInvoke call the method will fix the handleTemplate.handle with the returned value.
 798        // The caller is responsible for creating a correct SafeFreeContextBuffer_XXX flavor or null can be passed if no
 799        //
 800        private static unsafe int MustRunAcceptSecurityContext_SECURITY(
 801            ref SafeFreeCredentials inCredentials,
 802            bool isContextAbsent,
 803            Interop.SspiCli.SecBufferDesc* inputBuffer,
 804            Interop.SspiCli.ContextFlags inFlags,
 805            Interop.SspiCli.Endianness endianness,
 806            SafeDeleteContext outContext,
 807            ref Interop.SspiCli.SecBufferDesc outputBuffer,
 808            ref Interop.SspiCli.ContextFlags outFlags,
 809            SafeFreeContextBuffer? handleTemplate)
 810        {
 811            int errorCode = (int)Interop.SECURITY_STATUS.InvalidHandle;
 812
 813            bool mustReleaseCredentials = false;
 814            bool mustReleaseOutContext = false;
 815            // Run the body of this method as a non-interruptible block.
 816            try
 817            {
 818                inCredentials.DangerousAddRef(ref mustReleaseCredentials);
 819                outContext.DangerousAddRef(ref mustReleaseOutContext);
 820
 821                Interop.SspiCli.CredHandle credentialHandle = inCredentials._handle;
 822                long timeStamp;
 823
 824                // Now that "outContext" (or "refContext" by the caller) references an actual handle (and cannot
 825                // be closed until it is released below), point "inContextPtr" to its embedded handle (or
 826                // null if the embedded handle has not yet been initialized).
 827                Interop.SspiCli.CredHandle contextHandle = outContext._handle;
 828                void* inContextPtr = contextHandle.IsZero ? null : &contextHandle;
 829
 830                // The "isContextAbsent" supplied by the caller is generally correct but was computed without proper
 831                // synchronization. Rewrite the indicator now that the final "inContext" is known, update if necessary.
 832                isContextAbsent = (inContextPtr == null);
 833
 834                errorCode = Interop.SspiCli.AcceptSecurityContext(
 835                                ref credentialHandle,
 836                                inContextPtr,
 837                                inputBuffer,
 838                                inFlags,
 839                                endianness,
 840                                ref outContext._handle,
 841                                ref outputBuffer,
 842                                ref outFlags,
 843                                out timeStamp);
 844            }
 845            finally
 846            {
 847                //
 848                // When a credential handle is first associated with the context we keep credential
 849                // ref count bumped up to ensure ordered finalization.
 850                // If the credential handle has been changed we de-ref the old one and associate the
 851                //  context with the new cred handle but only if the call was successful.
 852                if (outContext._EffectiveCredential != inCredentials && (errorCode & 0x80000000) == 0)
 853                {
 854                    // Disassociate the previous credential handle.
 855                    outContext._EffectiveCredential?.DangerousRelease();
 856                    outContext._EffectiveCredential = inCredentials;
 857                }
 858                else if (mustReleaseCredentials)
 859                {
 860                    inCredentials.DangerousRelease();
 861                }
 862
 863                if (mustReleaseOutContext)
 864                {
 865                    outContext.DangerousRelease();
 866                }
 867            }
 868
 869            // The idea is that SSPI has allocated a block and filled up outUnmanagedBuffer+8 slot with the pointer.
 870            if (handleTemplate != null)
 871            {
 872                //ATTN: on 64 BIT that is still +8 cause of 2* c++ unsigned long == 8 bytes.
 873                handleTemplate.Set(((Interop.SspiCli.SecBuffer*)outputBuffer.pBuffers)->pvBuffer);
 874                if (handleTemplate.IsInvalid)
 875                {
 876                    handleTemplate.SetHandleAsInvalid();
 877                }
 878            }
 879
 880            if (isContextAbsent && (errorCode & 0x80000000) != 0)
 881            {
 882                // An error on the first call, need to set the out handle to invalid value.
 883                outContext._handle.SetToInvalid();
 884            }
 885
 886            return errorCode;
 887        }
 888
 889        internal static unsafe int CompleteAuthToken(
 890            ref SafeDeleteSslContext? refContext,
 891            in InputSecurityBuffer inSecBuffer)
 892        {
 893            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"refContext = {refContext}");
 894
 895            var inSecurityBufferDescriptor = new Interop.SspiCli.SecBufferDesc(1);
 896            int errorCode = (int)Interop.SECURITY_STATUS.InvalidHandle;
 897
 898            Interop.SspiCli.SecBuffer inUnmanagedBuffer = default;
 899            inSecurityBufferDescriptor.pBuffers = &inUnmanagedBuffer;
 900            fixed (byte* pinnedToken = inSecBuffer.Token)
 901            {
 902                Debug.Assert(inSecBuffer.UnmanagedToken != null);
 903                inUnmanagedBuffer.cbBuffer = inSecBuffer.Token.Length;
 904                inUnmanagedBuffer.BufferType = inSecBuffer.Type;
 905                inUnmanagedBuffer.pvBuffer =
 906                    inSecBuffer.Token.IsEmpty ? IntPtr.Zero : (IntPtr)pinnedToken;
 907
 908                Interop.SspiCli.CredHandle contextHandle = refContext != null ? refContext._handle : default;
 909                if (refContext == null || refContext.IsInvalid)
 910                {
 911                    // Previous versions unconditionally built a new "refContext" here, but would pass
 912                    // incorrect arguments to CompleteAuthToken in cases where a nonzero "contextHandle" was
 913                    // already present. In these cases, allow the "refContext" to flow through unmodified
 914                    // (which will generate an ObjectDisposedException below). In all other cases, continue to
 915                    // build a new "refContext" in an attempt to maximize compat.
 916                    if (contextHandle.IsZero)
 917                    {
 918                        refContext = new SafeDeleteSslContext();
 919                    }
 920                }
 921
 922                bool gotRef = false;
 923                try
 924                {
 925                    refContext!.DangerousAddRef(ref gotRef);
 926                    errorCode = Interop.SspiCli.CompleteAuthToken(contextHandle.IsZero ? null : &contextHandle, ref inSe
 927                }
 928                finally
 929                {
 930                    if (gotRef)
 931                    {
 932                        refContext!.DangerousRelease();
 933                    }
 934                }
 935            }
 936
 937            return errorCode;
 938        }
 939
 940        internal static unsafe int ApplyControlToken(
 941            ref SafeDeleteSslContext? refContext,
 942            in SecurityBuffer inSecBuffer)
 943        {
 944            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"refContext = {refContext}, inSecBuffer = {in
 945
 946            int errorCode = (int)Interop.SECURITY_STATUS.InvalidHandle;
 947
 948            // Fix Descriptor pointer that points to unmanaged SecurityBuffers.
 949            fixed (byte* pinnedInSecBufferToken = inSecBuffer.token)
 950            {
 951                var inSecurityBufferDescriptor = new Interop.SspiCli.SecBufferDesc(1);
 952                Interop.SspiCli.SecBuffer inUnmanagedBuffer = default;
 953                inSecurityBufferDescriptor.pBuffers = &inUnmanagedBuffer;
 954                inUnmanagedBuffer.cbBuffer = inSecBuffer.size;
 955                inUnmanagedBuffer.BufferType = inSecBuffer.type;
 956
 957                // Use the unmanaged token if it's not null; otherwise use the managed buffer.
 958                inUnmanagedBuffer.pvBuffer =
 959                    inSecBuffer.unmanagedToken != null ? inSecBuffer.unmanagedToken.DangerousGetHandle() :
 960                    inSecBuffer.token == null || inSecBuffer.token.Length == 0 ? IntPtr.Zero :
 961                    (IntPtr)(pinnedInSecBufferToken + inSecBuffer.offset);
 962
 963                Interop.SspiCli.CredHandle contextHandle = refContext != null ? refContext._handle : default;
 964
 965                if (refContext == null || refContext.IsInvalid)
 966                {
 967                    // Previous versions unconditionally built a new "refContext" here, but would pass
 968                    // incorrect arguments to ApplyControlToken in cases where a nonzero "contextHandle" was
 969                    // already present. In these cases, allow the "refContext" to flow through unmodified
 970                    // (which will generate an ObjectDisposedException below). In all other cases, continue to
 971                    // build a new "refContext" in an attempt to maximize compat.
 972                    if (contextHandle.IsZero)
 973                    {
 974                        refContext = new SafeDeleteSslContext();
 975                    }
 976                }
 977
 978                bool gotRef = false;
 979                try
 980                {
 981                    refContext!.DangerousAddRef(ref gotRef);
 982                    errorCode = Interop.SspiCli.ApplyControlToken(contextHandle.IsZero ? null : &contextHandle, ref inSe
 983                }
 984                finally
 985                {
 986                    if (gotRef)
 987                    {
 988                        refContext!.DangerousRelease();
 989                    }
 990                }
 991            }
 992
 993            return errorCode;
 994        }
 995    }
 996
 997    internal sealed class SafeDeleteSslContext : SafeDeleteContext
 998    {
 999        public SafeDeleteSslContext() : base() { }
 1000
 1001        protected override bool ReleaseHandle()
 1002        {
 1003            this._EffectiveCredential?.DangerousRelease();
 1004            return Interop.SspiCli.DeleteSecurityContext(ref _handle) == 0;
 1005        }
 1006    }
 1007
 1008    // Based on SafeFreeContextBuffer.
 1009    internal abstract class SafeFreeContextBufferChannelBinding : ChannelBinding
 1010    {
 1011        private int _size;
 1012
 1013        public override int Size
 1014        {
 1015            get { return _size; }
 1016        }
 1017
 1018        public override bool IsInvalid
 1019        {
 1020            get { return handle == new IntPtr(0) || handle == new IntPtr(-1); }
 1021        }
 1022
 1023        internal void Set(IntPtr value)
 1024        {
 1025            this.handle = value;
 1026        }
 1027
 1028        internal static SafeFreeContextBufferChannelBinding CreateEmptyHandle()
 1029        {
 1030            return new SafeFreeContextBufferChannelBinding_SECURITY();
 1031        }
 1032
 1033        public static unsafe int QueryContextChannelBinding(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribut
 1034        {
 1035            int status = (int)Interop.SECURITY_STATUS.InvalidHandle;
 1036
 1037            // SCHANNEL only supports SECPKG_ATTR_ENDPOINT_BINDINGS and SECPKG_ATTR_UNIQUE_BINDINGS which
 1038            // map to our enum ChannelBindingKind.Endpoint and ChannelBindingKind.Unique.
 1039            if (contextAttribute != Interop.SspiCli.ContextAttribute.SECPKG_ATTR_ENDPOINT_BINDINGS &&
 1040                contextAttribute != Interop.SspiCli.ContextAttribute.SECPKG_ATTR_UNIQUE_BINDINGS)
 1041            {
 1042                return status;
 1043            }
 1044
 1045            bool refAdded = false;
 1046            try
 1047            {
 1048                phContext.DangerousAddRef(ref refAdded);
 1049                status = Interop.SspiCli.QueryContextAttributesW(ref phContext._handle, contextAttribute, buffer);
 1050            }
 1051            finally
 1052            {
 1053                if (refAdded)
 1054                {
 1055                    phContext.DangerousRelease();
 1056                }
 1057            }
 1058
 1059            if (status == 0 && refHandle != null)
 1060            {
 1061                refHandle.Set((*buffer).Bindings);
 1062                refHandle._size = (*buffer).BindingsLength;
 1063            }
 1064
 1065            if (status != 0)
 1066            {
 1067                refHandle?.SetHandleAsInvalid();
 1068            }
 1069
 1070            return status;
 1071        }
 1072
 1073        public override string? ToString()
 1074        {
 1075            if (IsInvalid)
 1076            {
 1077                return null;
 1078            }
 1079
 1080            var bytes = new byte[_size];
 1081            Marshal.Copy(handle, bytes, 0, bytes.Length);
 1082            return BitConverter.ToString(bytes).Replace('-', ' ');
 1083        }
 1084    }
 1085
 1086    internal sealed class SafeFreeContextBufferChannelBinding_SECURITY : SafeFreeContextBufferChannelBinding
 1087    {
 1088        protected override bool ReleaseHandle()
 1089        {
 1090            return Interop.SspiCli.FreeContextBuffer(handle) == 0;
 1091        }
 1092    }
 1093}
 1094