< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 125
Coverable lines: 125
Total lines: 260
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 80
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%110%
.ctor(...)0%220%
GetHashCode()100%110%
Equals(...)0%220%
Equals(...)0%18180%
TryCachedCredential(...)0%18180%
GetCachedCredential(...)0%220%
CacheCredential(...)0%28280%
ShrinkCredentialCache()0%10100%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Net.Security/src/System/Net/Security/SslSessionsCache.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.Collections.Concurrent;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Diagnostics.CodeAnalysis;
 8using System.Security.Authentication;
 9using System.Security.Cryptography.X509Certificates;
 10
 11namespace System.Net.Security
 12{
 13    // Implements SSL session caching mechanism based on a static table of SSL credentials.
 14    internal static class SslSessionsCache
 15    {
 16        private const int CheckExpiredModulo = 32;
 017        private static readonly ConcurrentDictionary<SslCredKey, SafeCredentialReference> s_cachedCreds =
 018            new ConcurrentDictionary<SslCredKey, SafeCredentialReference>();
 19
 20        //
 21        // Uses certificate thumb-print comparison.
 22        //
 23        private readonly struct SslCredKey : IEquatable<SslCredKey>
 24        {
 25            private readonly byte[] _thumbPrint;
 26            private readonly int _allowedProtocols;
 27            private readonly EncryptionPolicy _encryptionPolicy;
 28            private readonly bool _isServerMode;
 29            private readonly bool _sendTrustList;
 30            private readonly bool _checkRevocation;
 31            private readonly bool _allowTlsResume;
 32            private readonly bool _allowRsaPssPadding;
 33            private readonly bool _allowRsaPkcs1Padding;
 34
 35            //
 36            // SECURITY: X509Certificate.GetCertHash() is virtual hence before going here,
 37            //           the caller of this ctor has to ensure that a user cert object was inspected and
 38            //           optionally cloned.
 39            //
 40            internal SslCredKey(
 41                byte[]? thumbPrint,
 42                int allowedProtocols,
 43                bool isServerMode,
 44                EncryptionPolicy encryptionPolicy,
 45                bool sendTrustList,
 46                bool checkRevocation,
 47                bool allowTlsResume,
 48                bool allowRsaPssPadding,
 49                bool allowRsaPkcs1Padding)
 050            {
 051                _thumbPrint = thumbPrint ?? Array.Empty<byte>();
 052                _allowedProtocols = allowedProtocols;
 053                _encryptionPolicy = encryptionPolicy;
 054                _isServerMode = isServerMode;
 055                _checkRevocation = checkRevocation;
 056                _sendTrustList = sendTrustList;
 057                _allowTlsResume = allowTlsResume;
 058                _allowRsaPssPadding = allowRsaPssPadding;
 059                _allowRsaPkcs1Padding = allowRsaPkcs1Padding;
 060            }
 61
 62            public override int GetHashCode()
 063            {
 064                HashCode hash = default;
 065                hash.AddBytes(_thumbPrint);
 066                hash.Add(_allowedProtocols);
 067                hash.Add((int)_encryptionPolicy);
 068                hash.Add(_isServerMode);
 069                hash.Add(_sendTrustList);
 070                hash.Add(_checkRevocation);
 071                hash.Add(_allowTlsResume);
 072                hash.Add(_allowRsaPssPadding);
 073                hash.Add(_allowRsaPkcs1Padding);
 74
 075                return hash.ToHashCode();
 076            }
 77
 78            public override bool Equals([NotNullWhen(true)] object? obj) =>
 079                obj is SslCredKey other && Equals(other);
 80
 81            public bool Equals(SslCredKey other)
 082            {
 083                byte[] thumbPrint = _thumbPrint;
 084                byte[] otherThumbPrint = other._thumbPrint;
 85
 086                return
 087                    thumbPrint.Length == otherThumbPrint.Length &&
 088                    _encryptionPolicy == other._encryptionPolicy &&
 089                    _allowedProtocols == other._allowedProtocols &&
 090                    _isServerMode == other._isServerMode &&
 091                    _sendTrustList == other._sendTrustList &&
 092                    _checkRevocation == other._checkRevocation &&
 093                    _allowTlsResume == other._allowTlsResume &&
 094                    _allowRsaPssPadding == other._allowRsaPssPadding &&
 095                    _allowRsaPkcs1Padding == other._allowRsaPkcs1Padding &&
 096                    thumbPrint.AsSpan().SequenceEqual(otherThumbPrint);
 097            }
 98        }
 99
 100        //
 101        // Returns null or a previously cached credential with an acquired reference.
 102        // The caller must release the reference after the security context has retained the credential.
 103        //
 104        internal static SafeFreeCredentials? TryCachedCredential(
 105            byte[]? thumbPrint,
 106            SslProtocols sslProtocols,
 107            bool isServer,
 108            EncryptionPolicy encryptionPolicy,
 109            bool checkRevocation,
 110            bool allowTlsResume,
 111            bool sendTrustList,
 112            bool allowRsaPssPadding,
 113            bool allowRsaPkcs1Padding)
 0114        {
 0115            if (s_cachedCreds.IsEmpty)
 0116            {
 0117                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"Not found, Current Cache Count = {s_cach
 0118                return null;
 119            }
 120
 0121            var key = new SslCredKey(thumbPrint, (int)sslProtocols, isServer, encryptionPolicy, sendTrustList, checkRevo
 122
 0123            SafeFreeCredentials? credentials = GetCachedCredential(key);
 0124            if (credentials == null || credentials.IsClosed || credentials.IsInvalid || credentials.Expiry < DateTime.Ut
 0125            {
 0126                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"Not found or invalid, Current Cache Coun
 0127                return null;
 128            }
 129
 0130            bool addedRef = false;
 131            try
 0132            {
 0133                credentials.DangerousAddRef(ref addedRef);
 0134            }
 0135            catch (ObjectDisposedException)
 0136            {
 137                // Cache scavenging may release the last reference between lookup and DangerousAddRef.
 0138                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, "Cached credential was closed before it co
 0139                return null;
 140            }
 141
 0142            if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"Found a cached Handle = {credentials}");
 143
 0144            return credentials;
 0145        }
 146
 147        private static SafeFreeCredentials? GetCachedCredential(SslCredKey key)
 0148        {
 0149            return s_cachedCreds.TryGetValue(key, out SafeCredentialReference? cached) ? cached.Target : null;
 0150        }
 151
 152        //
 153        // The app is calling this method after starting an SSL handshake.
 154        //
 155        // ATTN: The thumbPrint must be from inspected and possibly cloned user Cert object or we get a security hole in
 156        //
 157        internal static void CacheCredential(
 158            SafeFreeCredentials creds,
 159            byte[]? thumbPrint,
 160            SslProtocols sslProtocols,
 161            bool isServer,
 162            EncryptionPolicy encryptionPolicy,
 163            bool checkRevocation,
 164            bool allowTlsResume,
 165            bool sendTrustList,
 166            bool allowRsaPssPadding,
 167            bool allowRsaPkcs1Padding)
 0168        {
 0169            Debug.Assert(creds != null, "creds == null");
 170
 0171            if (creds.IsInvalid)
 0172            {
 0173                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"Refused to cache an Invalid Handle {cred
 0174                return;
 175            }
 176
 0177            SslCredKey key = new SslCredKey(thumbPrint, (int)sslProtocols, isServer, encryptionPolicy, sendTrustList, ch
 178
 0179            SafeFreeCredentials? credentials = GetCachedCredential(key);
 180
 0181            DateTime utcNow = DateTime.UtcNow;
 0182            if (credentials == null || credentials.IsClosed || credentials.IsInvalid || credentials.Expiry < utcNow)
 0183            {
 0184                lock (s_cachedCreds)
 0185                {
 0186                    credentials = GetCachedCredential(key);
 0187                    if (credentials == null || credentials.IsClosed || credentials.IsInvalid || credentials.Expiry < utc
 0188                    {
 0189                        SafeCredentialReference? cached = SafeCredentialReference.CreateReference(creds);
 190
 0191                        if (cached == null)
 0192                        {
 193                            // Means the handle got closed in between, return it back and let caller deal with the issue
 0194                            return;
 195                        }
 196
 0197                        s_cachedCreds[key] = cached;
 0198                        if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"Caching New Handle = {creds}, Cu
 199
 0200                        ShrinkCredentialCache();
 201
 0202                    }
 203                    else
 0204                    {
 0205                        if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"CacheCredential() (locked retry)
 0206                    }
 0207                }
 0208            }
 209            else
 0210            {
 0211                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"CacheCredential() Ignoring incoming hand
 0212            }
 213
 214            static void ShrinkCredentialCache()
 0215            {
 216
 217                //
 218                // A simplest way of preventing infinite cache grows.
 219                //
 220                // Security relief (DoS):
 221                //     A number of active creds is never greater than a number of _outstanding_
 222                //     security sessions, i.e. SSL connections.
 223                //     So we will try to shrink cache to the number of active creds once in a while.
 224                //
 225                //    We won't shrink cache in the case when NO new handles are coming to it.
 226                //
 0227                if ((s_cachedCreds.Count % CheckExpiredModulo) == 0)
 0228                {
 0229                    KeyValuePair<SslCredKey, SafeCredentialReference>[] toRemoveAttempt = s_cachedCreds.ToArray();
 230
 0231                    for (int i = 0; i < toRemoveAttempt.Length; ++i)
 0232                    {
 0233                        SafeCredentialReference? cached = toRemoveAttempt[i].Value;
 0234                        SafeFreeCredentials? creds = cached.Target;
 235
 0236                        if (creds == null)
 0237                        {
 0238                            s_cachedCreds.TryRemove(toRemoveAttempt[i].Key, out _);
 0239                            continue;
 240                        }
 241
 0242                        cached.Dispose();
 0243                        cached = SafeCredentialReference.CreateReference(creds);
 0244                        if (cached != null)
 0245                        {
 0246                            s_cachedCreds[toRemoveAttempt[i].Key] = cached;
 0247                        }
 248                        else
 0249                        {
 0250                            s_cachedCreds.TryRemove(toRemoveAttempt[i].Key, out _);
 0251                        }
 252
 0253                    }
 0254                    if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(null, $"Scavenged cache, New Cache Count = {
 0255                }
 0256            }
 0257        }
 258    }
 259}
 260