< Summary

Information
Class: Ice.SSL.TrustManager
Assembly: Ice
File(s): /_/csharp/src/Ice/SSL/TrustManager.cs
Tag: 99_23991109993
Line coverage
76%
Covered lines: 106
Uncovered lines: 33
Coverable lines: 139
Total lines: 303
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)

/_/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            // Decompose the subject DN into the RDNs.
 1122            if (_traceLevel > 0)
 123            {
 0124                if (info.incoming)
 125                {
 0126                    _communicator.getLogger().trace("Security", "trust manager evaluating client:\n" +
 0127                        "subject = " + subjectName + "\n" + "adapter = " + info.adapterName + "\n" + description);
 128                }
 129                else
 130                {
 0131                    _communicator.getLogger().trace("Security", "trust manager evaluating server:\n" +
 0132                        "subject = " + subjectName + "\n" + description);
 133                }
 134            }
 135
 136            List<RFC2253.RDNPair> dn;
 137            try
 138            {
 1139                dn = RFC2253.parseStrict(subjectName);
 1140            }
 0141            catch (ParseException e)
 142            {
 0143                _communicator.getLogger().warning(
 0144                    $"IceSSL: unable to parse certificate DN `{subjectName}'\nreason: {e.Message}");
 0145                return false;
 146            }
 147
 148            // Unescape the DN. Note that this isn't done in the parser in order to keep the various RFC2253
 149            // implementations as close as possible.
 1150            for (int i = 0; i < dn.Count; ++i)
 151            {
 1152                RFC2253.RDNPair p = dn[i];
 1153                p.value = RFC2253.unescape(p.value);
 1154                dn[i] = p;
 155            }
 156
 157            // Fail if we match anything in the reject set.
 1158            foreach (List<List<RFC2253.RDNPair>> matchSet in reject)
 159            {
 1160                if (_traceLevel > 0)
 161                {
 0162                    var s = new StringBuilder("trust manager rejecting PDNs:\n");
 0163                    stringify(matchSet, s);
 0164                    _communicator.getLogger().trace("Security", s.ToString());
 165                }
 1166                if (match(matchSet, dn))
 167                {
 1168                    return false;
 169                }
 170            }
 171
 172            // Succeed if we match anything in the accept set.
 1173            foreach (List<List<RFC2253.RDNPair>> matchSet in accept)
 174            {
 1175                if (_traceLevel > 0)
 176                {
 0177                    var s = new StringBuilder("trust manager accepting PDNs:\n");
 0178                    stringify(matchSet, s);
 0179                    _communicator.getLogger().trace("Security", s.ToString());
 180                }
 1181                if (match(matchSet, dn))
 182                {
 1183                    return true;
 184                }
 185            }
 186
 187            // At this point we accept the connection if there are no explicit accept rules.
 1188            return accept.Count == 0;
 189        }
 190
 0191        return false;
 1192    }
 193
 194    // Note that unlike the C++ & Java implementation this returns unescaped data.
 195    private static void parse(string value, List<List<RFC2253.RDNPair>> reject, List<List<RFC2253.RDNPair>> accept)
 196    {
 197        // As with the Java implementation, the DN that comes from the X500DistinguishedName does not necessarily match
 198        // the user's input form. Therefore we need to normalize the data to match the C# forms.
 1199        List<RFC2253.RDNEntry> l = RFC2253.parse(value);
 1200        for (int i = 0; i < l.Count; ++i)
 201        {
 1202            List<RFC2253.RDNPair> dn = l[i].rdn;
 1203            for (int j = 0; j < dn.Count; ++j)
 204            {
 1205                RFC2253.RDNPair pair = dn[j];
 206                // Normalize the RDN key.
 1207                if (pair.key == "emailAddress")
 208                {
 1209                    pair.key = "E";
 210                }
 1211                else if (pair.key == "ST")
 212                {
 1213                    pair.key = "S";
 214                }
 215                // Unescape the value.
 1216                pair.value = RFC2253.unescape(pair.value);
 1217                dn[j] = pair;
 218            }
 1219            if (l[i].negate)
 220            {
 1221                reject.Add(l[i].rdn);
 222            }
 223            else
 224            {
 1225                accept.Add(l[i].rdn);
 226            }
 227        }
 1228    }
 229
 230    private static void stringify(List<List<RFC2253.RDNPair>> matchSet, StringBuilder s)
 231    {
 0232        bool addSemi = false;
 0233        foreach (List<RFC2253.RDNPair> rdnSet in matchSet)
 234        {
 0235            if (addSemi)
 236            {
 0237                s.Append(';');
 238            }
 0239            addSemi = true;
 0240            bool addComma = false;
 0241            foreach (RFC2253.RDNPair rdn in rdnSet)
 242            {
 0243                if (addComma)
 244                {
 0245                    s.Append(',');
 246                }
 0247                addComma = true;
 0248                s.Append(rdn.key);
 0249                s.Append('=');
 0250                s.Append(rdn.value);
 251            }
 252        }
 0253    }
 254
 255    private bool match(List<List<RFC2253.RDNPair>> matchSet, List<RFC2253.RDNPair> subject)
 256    {
 1257        foreach (List<RFC2253.RDNPair> item in matchSet)
 258        {
 1259            if (matchRDNs(item, subject))
 260            {
 1261                return true;
 262            }
 263        }
 1264        return false;
 1265    }
 266
 267    private bool matchRDNs(List<RFC2253.RDNPair> match, List<RFC2253.RDNPair> subject)
 268    {
 1269        foreach (RFC2253.RDNPair matchRDN in match)
 270        {
 1271            bool found = false;
 1272            foreach (RFC2253.RDNPair subjectRDN in subject)
 273            {
 1274                if (matchRDN.key.Equals(subjectRDN.key, StringComparison.Ordinal))
 275                {
 1276                    found = true;
 1277                    if (!matchRDN.value.Equals(subjectRDN.value, StringComparison.Ordinal))
 278                    {
 1279                        return false;
 280                    }
 281                }
 282            }
 1283            if (!found)
 284            {
 0285                return false;
 286            }
 287        }
 1288        return true;
 1289    }
 290
 291    private readonly Ice.Communicator _communicator;
 292    private readonly int _traceLevel;
 293
 1294    private readonly List<List<RFC2253.RDNPair>> _rejectAll = [];
 1295    private readonly List<List<RFC2253.RDNPair>> _rejectClient = [];
 1296    private readonly List<List<RFC2253.RDNPair>> _rejectAllServer = [];
 1297    private readonly Dictionary<string, List<List<RFC2253.RDNPair>>> _rejectServer = [];
 298
 1299    private readonly List<List<RFC2253.RDNPair>> _acceptAll = [];
 1300    private readonly List<List<RFC2253.RDNPair>> _acceptClient = [];
 1301    private readonly List<List<RFC2253.RDNPair>> _acceptAllServer = [];
 1302    private readonly Dictionary<string, List<List<RFC2253.RDNPair>>> _acceptServer = [];
 303}