| | 1 | | // Copyright (c) ZeroC, Inc. |
| | 2 | |
|
| | 3 | | #nullable enable |
| | 4 | |
|
| | 5 | | using System.Diagnostics; |
| | 6 | | using System.Security.Cryptography.X509Certificates; |
| | 7 | | using System.Text; |
| | 8 | |
|
| | 9 | | namespace Ice.SSL; |
| | 10 | |
|
| | 11 | | internal sealed class TrustManager |
| | 12 | | { |
| 1 | 13 | | internal TrustManager(Ice.Communicator communicator) |
| | 14 | | { |
| | 15 | | Debug.Assert(communicator != null); |
| 1 | 16 | | _communicator = communicator; |
| 1 | 17 | | Ice.Properties properties = _communicator.getProperties(); |
| 1 | 18 | | _traceLevel = properties.getIcePropertyAsInt("IceSSL.Trace.Security"); |
| 1 | 19 | | string? key = null; |
| | 20 | | try |
| | 21 | | { |
| 1 | 22 | | key = "IceSSL.TrustOnly"; |
| 1 | 23 | | parse(properties.getIceProperty(key), _rejectAll, _acceptAll); |
| 1 | 24 | | key = "IceSSL.TrustOnly.Client"; |
| 1 | 25 | | parse(properties.getIceProperty(key), _rejectClient, _acceptClient); |
| 1 | 26 | | key = "IceSSL.TrustOnly.Server"; |
| 1 | 27 | | parse(properties.getIceProperty(key), _rejectAllServer, _acceptAllServer); |
| 1 | 28 | | Dictionary<string, string> dict = properties.getPropertiesForPrefix("IceSSL.TrustOnly.Server."); |
| 1 | 29 | | foreach (KeyValuePair<string, string> entry in dict) |
| | 30 | | { |
| 1 | 31 | | key = entry.Key; |
| 1 | 32 | | string name = key["IceSSL.TrustOnly.Server.".Length..]; |
| 1 | 33 | | var reject = new List<List<RFC2253.RDNPair>>(); |
| 1 | 34 | | var accept = new List<List<RFC2253.RDNPair>>(); |
| 1 | 35 | | parse(entry.Value, reject, accept); |
| 1 | 36 | | if (reject.Count > 0) |
| | 37 | | { |
| 1 | 38 | | _rejectServer[name] = reject; |
| | 39 | | } |
| 1 | 40 | | if (accept.Count > 0) |
| | 41 | | { |
| 1 | 42 | | _acceptServer[name] = accept; |
| | 43 | | } |
| | 44 | | } |
| 1 | 45 | | } |
| 0 | 46 | | catch (ParseException ex) |
| | 47 | | { |
| 0 | 48 | | throw new InitializationException($"IceSSL: invalid property {key}", ex); |
| | 49 | | } |
| 1 | 50 | | } |
| | 51 | |
|
| | 52 | | internal bool verify(ConnectionInfo info, string description) |
| | 53 | | { |
| 1 | 54 | | var reject = new List<List<List<RFC2253.RDNPair>>>(); |
| 1 | 55 | | var accept = new List<List<List<RFC2253.RDNPair>>>(); |
| | 56 | |
|
| 1 | 57 | | if (_rejectAll.Count != 0) |
| | 58 | | { |
| 1 | 59 | | reject.Add(_rejectAll); |
| | 60 | | } |
| 1 | 61 | | if (info.incoming) |
| | 62 | | { |
| 1 | 63 | | if (_rejectAllServer.Count != 0) |
| | 64 | | { |
| 1 | 65 | | reject.Add(_rejectAllServer); |
| | 66 | | } |
| 1 | 67 | | if (info.adapterName.Length > 0) |
| | 68 | | { |
| 1 | 69 | | if (_rejectServer.TryGetValue(info.adapterName, out List<List<RFC2253.RDNPair>>? p)) |
| | 70 | | { |
| 1 | 71 | | reject.Add(p); |
| | 72 | | } |
| | 73 | | } |
| | 74 | | } |
| | 75 | | else |
| | 76 | | { |
| 1 | 77 | | if (_rejectClient.Count != 0) |
| | 78 | | { |
| 1 | 79 | | reject.Add(_rejectClient); |
| | 80 | | } |
| | 81 | | } |
| | 82 | |
|
| 1 | 83 | | if (_acceptAll.Count != 0) |
| | 84 | | { |
| 1 | 85 | | accept.Add(_acceptAll); |
| | 86 | | } |
| 1 | 87 | | if (info.incoming) |
| | 88 | | { |
| 1 | 89 | | if (_acceptAllServer.Count != 0) |
| | 90 | | { |
| 1 | 91 | | accept.Add(_acceptAllServer); |
| | 92 | | } |
| 1 | 93 | | if (info.adapterName.Length > 0) |
| | 94 | | { |
| 1 | 95 | | if (_acceptServer.TryGetValue(info.adapterName, out List<List<RFC2253.RDNPair>>? p)) |
| | 96 | | { |
| 1 | 97 | | accept.Add(p); |
| | 98 | | } |
| | 99 | | } |
| | 100 | | } |
| | 101 | | else |
| | 102 | | { |
| 1 | 103 | | if (_acceptClient.Count != 0) |
| | 104 | | { |
| 1 | 105 | | accept.Add(_acceptClient); |
| | 106 | | } |
| | 107 | | } |
| | 108 | |
|
| | 109 | | // If there is nothing to match against, then we accept the cert. |
| 1 | 110 | | if (reject.Count == 0 && accept.Count == 0) |
| | 111 | | { |
| 1 | 112 | | return true; |
| | 113 | | } |
| | 114 | |
|
| | 115 | | // If there is no certificate then we match false. |
| 1 | 116 | | if (info.certs != null && info.certs.Length > 0) |
| | 117 | | { |
| 1 | 118 | | X500DistinguishedName subjectDN = info.certs[0].SubjectName; |
| 1 | 119 | | string subjectName = subjectDN.Name; |
| | 120 | | Debug.Assert(subjectName != null); |
| | 121 | | try |
| | 122 | | { |
| | 123 | | // Decompose the subject DN into the RDNs. |
| 1 | 124 | | if (_traceLevel > 0) |
| | 125 | | { |
| 0 | 126 | | if (info.incoming) |
| | 127 | | { |
| 0 | 128 | | _communicator.getLogger().trace("Security", "trust manager evaluating client:\n" + |
| 0 | 129 | | "subject = " + subjectName + "\n" + "adapter = " + info.adapterName + "\n" + description); |
| | 130 | | } |
| | 131 | | else |
| | 132 | | { |
| 0 | 133 | | _communicator.getLogger().trace("Security", "trust manager evaluating server:\n" + |
| 0 | 134 | | "subject = " + subjectName + "\n" + description); |
| | 135 | | } |
| | 136 | | } |
| | 137 | |
|
| 1 | 138 | | 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. |
| 1 | 142 | | for (int i = 0; i < dn.Count; ++i) |
| | 143 | | { |
| 1 | 144 | | RFC2253.RDNPair p = dn[i]; |
| 1 | 145 | | p.value = RFC2253.unescape(p.value); |
| 1 | 146 | | dn[i] = p; |
| | 147 | | } |
| | 148 | |
|
| | 149 | | // Fail if we match anything in the reject set. |
| 1 | 150 | | foreach (List<List<RFC2253.RDNPair>> matchSet in reject) |
| | 151 | | { |
| 1 | 152 | | if (_traceLevel > 0) |
| | 153 | | { |
| 0 | 154 | | var s = new StringBuilder("trust manager rejecting PDNs:\n"); |
| 0 | 155 | | stringify(matchSet, s); |
| 0 | 156 | | _communicator.getLogger().trace("Security", s.ToString()); |
| | 157 | | } |
| 1 | 158 | | if (match(matchSet, dn)) |
| | 159 | | { |
| 1 | 160 | | return false; |
| | 161 | | } |
| | 162 | | } |
| | 163 | |
|
| | 164 | | // Succeed if we match anything in the accept set. |
| 1 | 165 | | foreach (List<List<RFC2253.RDNPair>> matchSet in accept) |
| | 166 | | { |
| 1 | 167 | | if (_traceLevel > 0) |
| | 168 | | { |
| 0 | 169 | | var s = new StringBuilder("trust manager accepting PDNs:\n"); |
| 0 | 170 | | stringify(matchSet, s); |
| 0 | 171 | | _communicator.getLogger().trace("Security", s.ToString()); |
| | 172 | | } |
| 1 | 173 | | if (match(matchSet, dn)) |
| | 174 | | { |
| 1 | 175 | | return true; |
| | 176 | | } |
| | 177 | | } |
| 1 | 178 | | } |
| 0 | 179 | | catch (ParseException e) |
| | 180 | | { |
| 0 | 181 | | _communicator.getLogger().warning( |
| 0 | 182 | | $"IceSSL: unable to parse certificate DN `{subjectName}'\nreason: {e.Message}"); |
| 0 | 183 | | } |
| | 184 | |
|
| | 185 | | // At this point we accept the connection if there are no explicit accept rules. |
| 1 | 186 | | return accept.Count == 0; |
| | 187 | | } |
| | 188 | |
|
| 0 | 189 | | return false; |
| 1 | 190 | | } |
| | 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. |
| 1 | 197 | | List<RFC2253.RDNEntry> l = RFC2253.parse(value); |
| 1 | 198 | | for (int i = 0; i < l.Count; ++i) |
| | 199 | | { |
| 1 | 200 | | List<RFC2253.RDNPair> dn = l[i].rdn; |
| 1 | 201 | | for (int j = 0; j < dn.Count; ++j) |
| | 202 | | { |
| 1 | 203 | | RFC2253.RDNPair pair = dn[j]; |
| | 204 | | // Normalize the RDN key. |
| 1 | 205 | | if (pair.key == "emailAddress") |
| | 206 | | { |
| 1 | 207 | | pair.key = "E"; |
| | 208 | | } |
| 1 | 209 | | else if (pair.key == "ST") |
| | 210 | | { |
| 1 | 211 | | pair.key = "S"; |
| | 212 | | } |
| | 213 | | // Unescape the value. |
| 1 | 214 | | pair.value = RFC2253.unescape(pair.value); |
| 1 | 215 | | dn[j] = pair; |
| | 216 | | } |
| 1 | 217 | | if (l[i].negate) |
| | 218 | | { |
| 1 | 219 | | reject.Add(l[i].rdn); |
| | 220 | | } |
| | 221 | | else |
| | 222 | | { |
| 1 | 223 | | accept.Add(l[i].rdn); |
| | 224 | | } |
| | 225 | | } |
| 1 | 226 | | } |
| | 227 | |
|
| | 228 | | private static void stringify(List<List<RFC2253.RDNPair>> matchSet, StringBuilder s) |
| | 229 | | { |
| 0 | 230 | | bool addSemi = false; |
| 0 | 231 | | foreach (List<RFC2253.RDNPair> rdnSet in matchSet) |
| | 232 | | { |
| 0 | 233 | | if (addSemi) |
| | 234 | | { |
| 0 | 235 | | s.Append(';'); |
| | 236 | | } |
| 0 | 237 | | addSemi = true; |
| 0 | 238 | | bool addComma = false; |
| 0 | 239 | | foreach (RFC2253.RDNPair rdn in rdnSet) |
| | 240 | | { |
| 0 | 241 | | if (addComma) |
| | 242 | | { |
| 0 | 243 | | s.Append(','); |
| | 244 | | } |
| 0 | 245 | | addComma = true; |
| 0 | 246 | | s.Append(rdn.key); |
| 0 | 247 | | s.Append('='); |
| 0 | 248 | | s.Append(rdn.value); |
| | 249 | | } |
| | 250 | | } |
| 0 | 251 | | } |
| | 252 | |
|
| | 253 | | private bool match(List<List<RFC2253.RDNPair>> matchSet, List<RFC2253.RDNPair> subject) |
| | 254 | | { |
| 1 | 255 | | foreach (List<RFC2253.RDNPair> item in matchSet) |
| | 256 | | { |
| 1 | 257 | | if (matchRDNs(item, subject)) |
| | 258 | | { |
| 1 | 259 | | return true; |
| | 260 | | } |
| | 261 | | } |
| 1 | 262 | | return false; |
| 1 | 263 | | } |
| | 264 | |
|
| | 265 | | private bool matchRDNs(List<RFC2253.RDNPair> match, List<RFC2253.RDNPair> subject) |
| | 266 | | { |
| 1 | 267 | | foreach (RFC2253.RDNPair matchRDN in match) |
| | 268 | | { |
| 1 | 269 | | bool found = false; |
| 1 | 270 | | foreach (RFC2253.RDNPair subjectRDN in subject) |
| | 271 | | { |
| 1 | 272 | | if (matchRDN.key.Equals(subjectRDN.key, StringComparison.Ordinal)) |
| | 273 | | { |
| 1 | 274 | | found = true; |
| 1 | 275 | | if (!matchRDN.value.Equals(subjectRDN.value, StringComparison.Ordinal)) |
| | 276 | | { |
| 1 | 277 | | return false; |
| | 278 | | } |
| | 279 | | } |
| | 280 | | } |
| 1 | 281 | | if (!found) |
| | 282 | | { |
| 0 | 283 | | return false; |
| | 284 | | } |
| | 285 | | } |
| 1 | 286 | | return true; |
| 1 | 287 | | } |
| | 288 | |
|
| | 289 | | private readonly Ice.Communicator _communicator; |
| | 290 | | private readonly int _traceLevel; |
| | 291 | |
|
| 1 | 292 | | private readonly List<List<RFC2253.RDNPair>> _rejectAll = []; |
| 1 | 293 | | private readonly List<List<RFC2253.RDNPair>> _rejectClient = []; |
| 1 | 294 | | private readonly List<List<RFC2253.RDNPair>> _rejectAllServer = []; |
| 1 | 295 | | private readonly Dictionary<string, List<List<RFC2253.RDNPair>>> _rejectServer = []; |
| | 296 | |
|
| 1 | 297 | | private readonly List<List<RFC2253.RDNPair>> _acceptAll = []; |
| 1 | 298 | | private readonly List<List<RFC2253.RDNPair>> _acceptClient = []; |
| 1 | 299 | | private readonly List<List<RFC2253.RDNPair>> _acceptAllServer = []; |
| 1 | 300 | | private readonly Dictionary<string, List<List<RFC2253.RDNPair>>> _acceptServer = []; |
| | 301 | | } |