1 // Transport Security Layer (TLS) 2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez 3 // Copyright (C) 2006 Novell, Inc (http://www.novell.com) 4 // 5 // Permission is hereby granted, free of charge, to any person obtaining 6 // a copy of this software and associated documentation files (the 7 // "Software"), to deal in the Software without restriction, including 8 // without limitation the rights to use, copy, modify, merge, publish, 9 // distribute, sublicense, and/or sell copies of the Software, and to 10 // permit persons to whom the Software is furnished to do so, subject to 11 // the following conditions: 12 // 13 // The above copyright notice and this permission notice shall be 14 // included in all copies or substantial portions of the Software. 15 // 16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 // 24 25 using System; 26 using System.Collections; 27 using SSCX = System.Security.Cryptography.X509Certificates; 28 using Mono.Security.X509; 29 using Mono.Security.X509.Extensions; 30 31 namespace Mono.Security.Protocol.Tls.Handshake.Server 32 { 33 internal class TlsClientCertificate : HandshakeMessage 34 { 35 #region Fields 36 37 private X509CertificateCollection clientCertificates; 38 39 #endregion 40 41 #region Constructors 42 TlsClientCertificate(Context context, byte[] buffer)43 public TlsClientCertificate(Context context, byte[] buffer) 44 : base(context, HandshakeType.Certificate, buffer) 45 { 46 } 47 48 #endregion 49 50 #region Methods 51 Update()52 public override void Update() 53 { 54 foreach (X509Certificate certificate in clientCertificates) { 55 this.Context.ClientSettings.Certificates.Add (new SSCX.X509Certificate (certificate.RawData)); 56 } 57 } 58 59 public bool HasCertificate { 60 get { return clientCertificates.Count > 0; } 61 } 62 63 #endregion 64 65 #region Protected Methods 66 ProcessAsSsl3()67 protected override void ProcessAsSsl3() 68 { 69 this.ProcessAsTls1(); 70 } 71 ProcessAsTls1()72 protected override void ProcessAsTls1() 73 { 74 int bytesRead = 0; 75 int length = this.ReadInt24 (); 76 this.clientCertificates = new X509CertificateCollection (); 77 while (length > bytesRead) { 78 int certLength = this.ReadInt24 (); 79 bytesRead += certLength + 3; 80 byte[] cert = this.ReadBytes (certLength); 81 this.clientCertificates.Add (new X509Certificate (cert)); 82 } 83 84 if (this.clientCertificates.Count > 0) 85 { 86 this.validateCertificates (this.clientCertificates); 87 } 88 else if ((this.Context as ServerContext).ClientCertificateRequired) 89 { 90 throw new TlsException (AlertDescription.NoCertificate); 91 } 92 } 93 94 #endregion 95 96 #region Private Methods 97 checkCertificateUsage(X509Certificate cert)98 private bool checkCertificateUsage (X509Certificate cert) 99 { 100 ServerContext context = (ServerContext)this.Context; 101 102 // certificate extensions are required for this 103 // we "must" accept older certificates without proofs 104 if (cert.Version < 3) 105 return true; 106 107 KeyUsages ku = KeyUsages.none; 108 switch (context.Negotiating.Cipher.ExchangeAlgorithmType) 109 { 110 case ExchangeAlgorithmType.RsaSign: 111 case ExchangeAlgorithmType.RsaKeyX: 112 ku = KeyUsages.digitalSignature; 113 break; 114 case ExchangeAlgorithmType.DiffieHellman: 115 ku = KeyUsages.keyAgreement; 116 break; 117 case ExchangeAlgorithmType.Fortezza: 118 return false; // unsupported certificate type 119 } 120 121 KeyUsageExtension kux = null; 122 ExtendedKeyUsageExtension eku = null; 123 124 X509Extension xtn = cert.Extensions["2.5.29.15"]; 125 if (xtn != null) 126 kux = new KeyUsageExtension (xtn); 127 128 xtn = cert.Extensions["2.5.29.37"]; 129 if (xtn != null) 130 eku = new ExtendedKeyUsageExtension (xtn); 131 132 if ((kux != null) && (eku != null)) 133 { 134 // RFC3280 states that when both KeyUsageExtension and 135 // ExtendedKeyUsageExtension are present then BOTH should 136 // be valid 137 return (kux.Support (ku) && 138 eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.2")); 139 } 140 else if (kux != null) 141 { 142 return kux.Support (ku); 143 } 144 else if (eku != null) 145 { 146 // Client Authentication (1.3.6.1.5.5.7.3.2) 147 return eku.KeyPurpose.Contains ("1.3.6.1.5.5.7.3.2"); 148 } 149 150 // last chance - try with older (deprecated) Netscape extensions 151 xtn = cert.Extensions["2.16.840.1.113730.1.1"]; 152 if (xtn != null) 153 { 154 NetscapeCertTypeExtension ct = new NetscapeCertTypeExtension (xtn); 155 return ct.Support (NetscapeCertTypeExtension.CertTypes.SslClient); 156 } 157 158 // certificate isn't valid for SSL server usage 159 return false; 160 } 161 validateCertificates(X509CertificateCollection certificates)162 private void validateCertificates (X509CertificateCollection certificates) 163 { 164 ServerContext context = (ServerContext)this.Context; 165 AlertDescription description = AlertDescription.BadCertificate; 166 SSCX.X509Certificate client = null; 167 int[] certificateErrors = null; 168 169 // note: certificate may be null is no certificate is sent 170 // (e.g. optional mutual authentication) 171 if (certificates.Count > 0) { 172 X509Certificate leaf = certificates[0]; 173 174 ArrayList errors = new ArrayList (); 175 176 // SSL specific check - not all certificates can be 177 // used to server-side SSL some rules applies after 178 // all ;-) 179 if (!checkCertificateUsage (leaf)) 180 { 181 // WinError.h CERT_E_PURPOSE 0x800B0106 182 errors.Add ((int)-2146762490); 183 } 184 185 X509Chain verify; 186 // was a chain supplied ? if so use it, if not 187 if (certificates.Count > 1) { 188 // if so use it (and don't build our own) 189 X509CertificateCollection chain = new X509CertificateCollection (certificates); 190 chain.Remove (leaf); 191 verify = new X509Chain (chain); 192 } else { 193 // if not, then let's build our own (based on what's available in the stores) 194 verify = new X509Chain (); 195 } 196 197 bool result = false; 198 199 try 200 { 201 result = verify.Build (leaf); 202 } 203 catch (Exception) 204 { 205 result = false; 206 } 207 208 if (!result) 209 { 210 switch (verify.Status) 211 { 212 case X509ChainStatusFlags.InvalidBasicConstraints: 213 // WinError.h TRUST_E_BASIC_CONSTRAINTS 0x80096019 214 errors.Add ((int)-2146869223); 215 break; 216 217 case X509ChainStatusFlags.NotSignatureValid: 218 // WinError.h TRUST_E_BAD_DIGEST 0x80096010 219 errors.Add ((int)-2146869232); 220 break; 221 222 case X509ChainStatusFlags.NotTimeNested: 223 // WinError.h CERT_E_VALIDITYPERIODNESTING 0x800B0102 224 errors.Add ((int)-2146762494); 225 break; 226 227 case X509ChainStatusFlags.NotTimeValid: 228 // WinError.h CERT_E_EXPIRED 0x800B0101 229 description = AlertDescription.CertificateExpired; 230 errors.Add ((int)-2146762495); 231 break; 232 233 case X509ChainStatusFlags.PartialChain: 234 // WinError.h CERT_E_CHAINING 0x800B010A 235 description = AlertDescription.UnknownCA; 236 errors.Add ((int)-2146762486); 237 break; 238 239 case X509ChainStatusFlags.UntrustedRoot: 240 // WinError.h CERT_E_UNTRUSTEDROOT 0x800B0109 241 description = AlertDescription.UnknownCA; 242 errors.Add ((int)-2146762487); 243 break; 244 245 default: 246 // unknown error 247 description = AlertDescription.CertificateUnknown; 248 errors.Add ((int)verify.Status); 249 break; 250 } 251 } 252 client = new SSCX.X509Certificate (leaf.RawData); 253 certificateErrors = (int[])errors.ToArray (typeof (int)); 254 } 255 else 256 { 257 certificateErrors = new int[0]; 258 } 259 260 SSCX.X509CertificateCollection certCollection = new SSCX.X509CertificateCollection (); 261 foreach (X509Certificate certificate in certificates) { 262 certCollection.Add (new SSCX.X509Certificate (certificate.RawData)); 263 } 264 if (!context.SslStream.RaiseClientCertificateValidation(client, certificateErrors)) 265 { 266 throw new TlsException ( 267 description, 268 "Invalid certificate received from client."); 269 } 270 271 this.Context.ClientSettings.ClientCertificate = client; 272 } 273 274 #endregion 275 } 276 } 277