< Summary

Information
Class: Ice.SSL.TrustManager
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/SSL/TrustManager.cs
Tag: 71_18251537082
Line coverage
76%
Covered lines: 106
Uncovered lines: 33
Coverable lines: 139
Total lines: 301
Line coverage: 76.2%
Branch coverage
81%
Covered branches: 72
Total branches: 88
Branch coverage: 81.8%
Method coverage
83%
Covered methods: 5
Total methods: 6
Method coverage: 83.3%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%6.01694.12%
verify(...)86%97.435073.33%
parse(...)100%1010100%
stringify(...)0%7280%
match(...)100%44100%
matchRDNs(...)90%10.081090.91%

File(s)

/home/runner/work/ice/ice/csharp/src/Ice/SSL/TrustManager.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3#nullable enable
 4
 5using System.Diagnostics;
 6using System.Security.Cryptography.X509Certificates;
 7using System.Text;
 8
 9namespace Ice.SSL;
 10
 11internal sealed class TrustManager
 12{
 113    internal TrustManager(Ice.Communicator communicator)
 14    {
 15        Debug.Assert(communicator != null);
 116        _communicator = communicator;
 117        Ice.Properties properties = _communicator.getProperties();
 118        _traceLevel = properties.getIcePropertyAsInt("IceSSL.Trace.Security");
 119        string? key = null;
 20        try
 21        {
 122            key = "IceSSL.TrustOnly";
 123            parse(properties.getIceProperty(key), _rejectAll, _acceptAll);
 124            key = "IceSSL.TrustOnly.Client";
 125            parse(properties.getIceProperty(key), _rejectClient, _acceptClient);
 126            key = "IceSSL.TrustOnly.Server";
 127            parse(properties.getIceProperty(key), _rejectAllServer, _acceptAllServer);
 128            Dictionary<string, string> dict = properties.getPropertiesForPrefix("IceSSL.TrustOnly.Server.");
 129            foreach (KeyValuePair<string, string> entry in dict)
 30            {
 131                key = entry.Key;
 132                string name = key["IceSSL.TrustOnly.Server.".Length..];
 133                var reject = new List<List<RFC2253.RDNPair>>();
 134                var accept = new List<List<RFC2253.RDNPair>>();
 135                parse(entry.Value, reject, accept);
 136                if (reject.Count > 0)
 37                {
 138                    _rejectServer[name] = reject;
 39                }
 140                if (accept.Count > 0)
 41                {
 142                    _acceptServer[name] = accept;
 43                }
 44            }
 145        }
 046        catch (ParseException ex)
 47        {
 048            throw new InitializationException($"IceSSL: invalid property {key}", ex);
 49        }
 150    }
 51
 52    internal bool verify(ConnectionInfo info, string description)
 53    {
 154        var reject = new List<List<List<RFC2253.RDNPair>>>();
 155        var accept = new List<List<List<RFC2253.RDNPair>>>();
 56
 157        if (_rejectAll.Count != 0)
 58        {
 159            reject.Add(_rejectAll);
 60        }
 161        if (info.incoming)
 62        {
 163            if (_rejectAllServer.Count != 0)
 64            {
 165                reject.Add(_rejectAllServer);
 66            }
 167            if (info.adapterName.Length > 0)
 68            {
 169                if (_rejectServer.TryGetValue(info.adapterName, out List<List<RFC2253.RDNPair>>? p))
 70                {
 171                    reject.Add(p);
 72                }
 73            }
 74        }
 75        else
 76        {
 177            if (_rejectClient.Count != 0)
 78            {
 179                reject.Add(_rejectClient);
 80            }
 81        }
 82
 183        if (_acceptAll.Count != 0)
 84        {
 185            accept.Add(_acceptAll);
 86        }
 187        if (info.incoming)
 88        {
 189            if (_acceptAllServer.Count != 0)
 90            {
 191                accept.Add(_acceptAllServer);
 92            }
 193            if (info.adapterName.Length > 0)
 94            {
 195                if (_acceptServer.TryGetValue(info.adapterName, out List<List<RFC2253.RDNPair>>? p))
 96                {
 197                    accept.Add(p);
 98                }
 99            }
 100        }
 101        else
 102        {
 1103            if (_acceptClient.Count != 0)
 104            {
 1105                accept.Add(_acceptClient);
 106            }
 107        }
 108
 109        // If there is nothing to match against, then we accept the cert.
 1110        if (reject.Count == 0 && accept.Count == 0)
 111        {
 1112            return true;
 113        }
 114
 115        // If there is no certificate then we match false.
 1116        if (info.certs != null && info.certs.Length > 0)
 117        {
 1118            X500DistinguishedName subjectDN = info.certs[0].SubjectName;
 1119            string subjectName = subjectDN.Name;
 120            Debug.Assert(subjectName != null);
 121            try
 122            {
 123                // Decompose the subject DN into the RDNs.
 1124                if (_traceLevel > 0)
 125                {
 0126                    if (info.incoming)
 127                    {
 0128                        _communicator.getLogger().trace("Security", "trust manager evaluating client:\n" +
 0129                            "subject = " + subjectName + "\n" + "adapter = " + info.adapterName + "\n" + description);
 130                    }
 131                    else
 132                    {
 0133                        _communicator.getLogger().trace("Security", "trust manager evaluating server:\n" +
 0134                            "subject = " + subjectName + "\n" + description);
 135                    }
 136                }
 137
 1138                List<RFC2253.RDNPair> dn = RFC2253.parseStrict(subjectName);
 139
 140                // Unescape the DN. Note that this isn't done in the parser in order to keep the various RFC2253
 141                // implementations as close as possible.
 1142                for (int i = 0; i < dn.Count; ++i)
 143                {
 1144                    RFC2253.RDNPair p = dn[i];
 1145                    p.value = RFC2253.unescape(p.value);
 1146                    dn[i] = p;
 147                }
 148
 149                // Fail if we match anything in the reject set.
 1150                foreach (List<List<RFC2253.RDNPair>> matchSet in reject)
 151                {
 1152                    if (_traceLevel > 0)
 153                    {
 0154                        var s = new StringBuilder("trust manager rejecting PDNs:\n");
 0155                        stringify(matchSet, s);
 0156                        _communicator.getLogger().trace("Security", s.ToString());
 157                    }
 1158                    if (match(matchSet, dn))
 159                    {
 1160                        return false;
 161                    }
 162                }
 163
 164                // Succeed if we match anything in the accept set.
 1165                foreach (List<List<RFC2253.RDNPair>> matchSet in accept)
 166                {
 1167                    if (_traceLevel > 0)
 168                    {
 0169                        var s = new StringBuilder("trust manager accepting PDNs:\n");
 0170                        stringify(matchSet, s);
 0171                        _communicator.getLogger().trace("Security", s.ToString());
 172                    }
 1173                    if (match(matchSet, dn))
 174                    {
 1175                        return true;
 176                    }
 177                }
 1178            }
 0179            catch (ParseException e)
 180            {
 0181                _communicator.getLogger().warning(
 0182                    $"IceSSL: unable to parse certificate DN `{subjectName}'\nreason: {e.Message}");
 0183            }
 184
 185            // At this point we accept the connection if there are no explicit accept rules.
 1186            return accept.Count == 0;
 187        }
 188
 0189        return false;
 1190    }
 191
 192    // Note that unlike the C++ & Java implementation this returns unescaped data.
 193    private static void parse(string value, List<List<RFC2253.RDNPair>> reject, List<List<RFC2253.RDNPair>> accept)
 194    {
 195        // As with the Java implementation, the DN that comes from the X500DistinguishedName does not necessarily match
 196        // the user's input form. Therefore we need to normalize the data to match the C# forms.
 1197        List<RFC2253.RDNEntry> l = RFC2253.parse(value);
 1198        for (int i = 0; i < l.Count; ++i)
 199        {
 1200            List<RFC2253.RDNPair> dn = l[i].rdn;
 1201            for (int j = 0; j < dn.Count; ++j)
 202            {
 1203                RFC2253.RDNPair pair = dn[j];
 204                // Normalize the RDN key.
 1205                if (pair.key == "emailAddress")
 206                {
 1207                    pair.key = "E";
 208                }
 1209                else if (pair.key == "ST")
 210                {
 1211                    pair.key = "S";
 212                }
 213                // Unescape the value.
 1214                pair.value = RFC2253.unescape(pair.value);
 1215                dn[j] = pair;
 216            }
 1217            if (l[i].negate)
 218            {
 1219                reject.Add(l[i].rdn);
 220            }
 221            else
 222            {
 1223                accept.Add(l[i].rdn);
 224            }
 225        }
 1226    }
 227
 228    private static void stringify(List<List<RFC2253.RDNPair>> matchSet, StringBuilder s)
 229    {
 0230        bool addSemi = false;
 0231        foreach (List<RFC2253.RDNPair> rdnSet in matchSet)
 232        {
 0233            if (addSemi)
 234            {
 0235                s.Append(';');
 236            }
 0237            addSemi = true;
 0238            bool addComma = false;
 0239            foreach (RFC2253.RDNPair rdn in rdnSet)
 240            {
 0241                if (addComma)
 242                {
 0243                    s.Append(',');
 244                }
 0245                addComma = true;
 0246                s.Append(rdn.key);
 0247                s.Append('=');
 0248                s.Append(rdn.value);
 249            }
 250        }
 0251    }
 252
 253    private bool match(List<List<RFC2253.RDNPair>> matchSet, List<RFC2253.RDNPair> subject)
 254    {
 1255        foreach (List<RFC2253.RDNPair> item in matchSet)
 256        {
 1257            if (matchRDNs(item, subject))
 258            {
 1259                return true;
 260            }
 261        }
 1262        return false;
 1263    }
 264
 265    private bool matchRDNs(List<RFC2253.RDNPair> match, List<RFC2253.RDNPair> subject)
 266    {
 1267        foreach (RFC2253.RDNPair matchRDN in match)
 268        {
 1269            bool found = false;
 1270            foreach (RFC2253.RDNPair subjectRDN in subject)
 271            {
 1272                if (matchRDN.key.Equals(subjectRDN.key, StringComparison.Ordinal))
 273                {
 1274                    found = true;
 1275                    if (!matchRDN.value.Equals(subjectRDN.value, StringComparison.Ordinal))
 276                    {
 1277                        return false;
 278                    }
 279                }
 280            }
 1281            if (!found)
 282            {
 0283                return false;
 284            }
 285        }
 1286        return true;
 1287    }
 288
 289    private readonly Ice.Communicator _communicator;
 290    private readonly int _traceLevel;
 291
 1292    private readonly List<List<RFC2253.RDNPair>> _rejectAll = [];
 1293    private readonly List<List<RFC2253.RDNPair>> _rejectClient = [];
 1294    private readonly List<List<RFC2253.RDNPair>> _rejectAllServer = [];
 1295    private readonly Dictionary<string, List<List<RFC2253.RDNPair>>> _rejectServer = [];
 296
 1297    private readonly List<List<RFC2253.RDNPair>> _acceptAll = [];
 1298    private readonly List<List<RFC2253.RDNPair>> _acceptClient = [];
 1299    private readonly List<List<RFC2253.RDNPair>> _acceptAllServer = [];
 1300    private readonly Dictionary<string, List<List<RFC2253.RDNPair>>> _acceptServer = [];
 301}