Add licensing info
[mono.git] / mcs / class / Mono.Security / Mono.Security.X509 / X509Chain.cs
1 //
2 // X509Chain.cs: X.509 Certificate Path
3 //      This is a VERY simplified and minimal version
4 //      used for
5 //              Authenticode support
6 //              TLS/SSL support
7 //
8 // Author:
9 //      Sebastien Pouliot  <sebastien@ximian.com>
10 //
11 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
12 // (C) 2004 Novell (http://www.novell.com)
13 //
14
15 //
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 //
35
36 using System;
37 using System.Security;
38 using System.Security.Permissions;
39
40 #if !INSIDE_CORLIB
41 using System.Net;
42 #endif
43
44 using Mono.Security.X509.Extensions;
45
46 namespace Mono.Security.X509 {
47
48 #if INSIDE_CORLIB
49         internal
50 #else
51         public 
52 #endif
53         class X509Chain {
54
55                 private X509CertificateCollection roots;
56                 private X509CertificateCollection certs;
57                 private X509Certificate _root;
58
59                 private X509CertificateCollection _chain;
60                 private X509ChainStatusFlags _status;
61
62                 // constructors
63
64                 public X509Chain ()
65                 {
66                         certs = new X509CertificateCollection ();
67                 }
68
69                 // get a pre-builded chain
70                 public X509Chain (X509CertificateCollection chain) : this ()
71                 {
72                         _chain = new X509CertificateCollection ();
73                         _chain.AddRange (chain);
74                 }
75
76                 // properties
77
78                 public X509CertificateCollection Chain {
79                         get { return _chain; }
80                 }
81
82                 // the root of the specified certificate (may not be trusted!)
83                 public X509Certificate Root {
84                         get { return _root; }
85                 }
86
87                 public X509ChainStatusFlags Status {
88                         get { return _status; }
89                 }
90
91                 public X509CertificateCollection TrustAnchors {
92                         get { 
93                                 if (roots == null) {
94                                         roots = new X509CertificateCollection ();
95                                         roots.AddRange (X509StoreManager.TrustedRootCertificates);
96                                         return roots;
97                                 }
98                                 return roots;
99                         }
100                         [SecurityPermission (SecurityAction.Demand, Flags=SecurityPermissionFlag.ControlPolicy)]
101                         set { roots = value; }
102                 }
103
104                 // methods
105
106                 public void LoadCertificate (X509Certificate x509) 
107                 {
108                         certs.Add (x509);
109                 }
110
111                 public void LoadCertificates (X509CertificateCollection collection) 
112                 {
113                         certs.AddRange (collection);
114                 }
115
116                 public X509Certificate FindByIssuerName (string issuerName) 
117                 {
118                         foreach (X509Certificate x in certs) {
119                                 if (x.IssuerName == issuerName)
120                                         return x;
121                         }
122                         return null;
123                 }
124
125                 public bool Build (X509Certificate leaf) 
126                 {
127                         _status = X509ChainStatusFlags.NoError;
128                         if (_chain == null) {
129                                 // chain not supplied - we must built it ourselve
130                                 _chain = new X509CertificateCollection ();
131                                 X509Certificate x = leaf;
132                                 X509Certificate tmp = null;
133                                 while ((x != null) && (!x.IsSelfSigned)) {
134                                         tmp = FindCertificateParent (x);
135                                         if (x != null) {
136                                                 _chain.Add (x);
137                                                 tmp = x;        // last valid
138                                         }
139                                 }
140                                 // find a trusted root
141                                 _root = FindCertificateRoot (tmp);
142                         }
143                         else {
144                                 // chain supplied - still have to check signatures!
145                                 int last = _chain.Count;
146                                 if (last > 0) {
147                                         if (IsParent (leaf, _chain [0])) {
148                                                 int i = 1;
149                                                 for (; i < last; i++) {
150                                                         if (!IsParent (_chain [i-1], _chain [i]))
151                                                                 break;
152                                                 }
153                                                 if (i == last)
154                                                         _root = FindCertificateRoot (_chain [last - 1]);
155                                         }
156                                 }
157                                 else {
158                                         // is the leaf a root ? (trusted or untrusted)
159                                         _root = FindCertificateRoot (leaf);
160                                 }
161                         }
162
163                         // validate the chain
164                         if ((_chain != null) && (_status == X509ChainStatusFlags.NoError)) {
165                                 foreach (X509Certificate x in _chain) {
166                                         // validate dates for each certificate in the chain
167                                         // note: we DO NOT check for nested date/time
168                                         if (!IsValid (x)) {
169                                                 return false;
170                                         }
171                                 }
172                                 // check leaf
173                                 if (!IsValid (leaf)) {
174                                         // switch status code if the failure is expiration
175                                         if (_status == X509ChainStatusFlags.NotTimeNested)
176                                                 _status = X509ChainStatusFlags.NotTimeValid;
177                                         return false;
178                                 }
179                                 // check root
180                                 if ((_root != null) && !IsValid (_root)) {
181                                         return false;
182                                 }
183                         }
184                         return (_status == X509ChainStatusFlags.NoError);
185                 }
186
187                 //
188
189                 public void Reset () 
190                 {
191                         _status = X509ChainStatusFlags.NoError;
192                         roots = null; // this force a reload
193                         certs.Clear ();
194                         _chain.Clear ();
195                 }
196
197                 // private stuff
198
199                 private bool IsValid (X509Certificate cert) 
200                 {
201                         if (!cert.IsCurrent) {
202                                 // FIXME: nesting isn't very well implemented
203                                 _status = X509ChainStatusFlags.NotTimeNested;
204                                 return false;
205                         }
206
207                         // TODO - we should check for CRITICAL but unknown extensions
208                         // X509ChainStatusFlags.InvalidExtension
209 #if (!NET_1_0 && !INSIDE_CORLIB)
210                         if (ServicePointManager.CheckCertificateRevocationList) {
211                                 // TODO - check revocation (CRL, OCSP ...)
212                                 // X509ChainStatusFlags.RevocationStatusUnknown
213                                 // X509ChainStatusFlags.Revoked
214                         }
215 #endif
216                         return true;
217                 }
218
219                 private X509Certificate FindCertificateParent (X509Certificate child) 
220                 {
221                         foreach (X509Certificate potentialParent in certs) {
222                                 if (IsParent (child, potentialParent))
223                                         return potentialParent;
224                         }
225                         return null;
226                 }
227
228                 private X509Certificate FindCertificateRoot (X509Certificate potentialRoot) 
229                 {
230                         if (potentialRoot == null) {
231                                 _status = X509ChainStatusFlags.PartialChain;
232                                 return null;
233                         }
234
235                         // if the trusted root is in the chain
236                         if (IsTrusted (potentialRoot)) {
237                                 return potentialRoot;
238                         }
239
240                         // if the root isn't in the chain
241                         foreach (X509Certificate root in TrustAnchors) {
242                                 if (IsParent (potentialRoot, root)) {
243                                         return root;
244                                 }
245                         }
246
247                         // is it a (untrusted) root ?
248                         if (potentialRoot.IsSelfSigned) {
249                                 _status = X509ChainStatusFlags.UntrustedRoot;
250                                 return potentialRoot;
251                         }
252
253                         _status = X509ChainStatusFlags.PartialChain;
254                         return null;
255                 }
256
257                 private bool IsTrusted (X509Certificate potentialTrusted) 
258                 {
259                         return TrustAnchors.Contains (potentialTrusted);
260                 }
261
262                 private bool IsParent (X509Certificate child, X509Certificate parent) 
263                 {
264                         if (child.IssuerName != parent.SubjectName)
265                                 return false;
266
267                         // parent MUST have the Basic Constraint CA=true (except for trusted roots)
268                         // see why at http://www.microsoft.com/technet/security/bulletin/MS02-050.asp
269                         if ((parent.Version > 2) && (!IsTrusted (parent))) {
270                                 // TODO: we do not support pathLenConstraint
271                                 X509Extension ext = parent.Extensions ["2.5.29.19"];
272                                 if (ext != null) {
273                                         BasicConstraintsExtension bc = new BasicConstraintsExtension (ext);
274                                         if (!bc.CertificateAuthority)
275                                                 _status = X509ChainStatusFlags.InvalidBasicConstraints;
276                                 }
277                                 else
278                                         _status = X509ChainStatusFlags.InvalidBasicConstraints;
279                         }
280
281                         if (!child.VerifySignature (parent.RSA)) {
282                                 _status = X509ChainStatusFlags.NotSignatureValid;
283                                 return false;
284                         }
285                         return true;
286                 }
287         }
288 }