Added tests for Task.WhenAll w/ empty list
[mono.git] / mcs / class / corlib / Mono.Security.X509 / X509Store.cs
1 //
2 // X509Store.cs: Handles a X.509 certificates/CRLs store
3 //
4 // Author:
5 //      Sebastien Pouliot  <sebastien@ximian.com>
6 //      Pablo Ruiz <pruiz@netway.org>
7 //
8 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
9 // (C) 2010 Pablo Ruiz.
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System;
32 using System.Collections;
33 using System.Globalization;
34 using System.IO;
35 using System.Text;
36 using System.Security.Cryptography;
37
38 using Mono.Security.Cryptography;
39 using Mono.Security.X509.Extensions;
40
41 namespace Mono.Security.X509 {
42
43 #if INSIDE_CORLIB
44         internal
45 #else
46         public 
47 #endif
48         class X509Store {
49
50                 private string _storePath;
51                 private X509CertificateCollection _certificates;
52                 private ArrayList _crls;
53                 private bool _crl;
54                 private string _name;
55
56                 internal X509Store (string path, bool crl) 
57                 {
58                         _storePath = path;
59                         _crl = crl;
60                 }
61
62                 // properties
63
64                 public X509CertificateCollection Certificates {
65                         get { 
66                                 if (_certificates == null) {
67                                         _certificates = BuildCertificatesCollection (_storePath);
68                                 }
69                                 return _certificates; 
70                         }
71                 }
72
73                 public ArrayList Crls {
74                         get {
75                                 // CRL aren't applicable to all stores
76                                 // but returning null is a little rude
77                                 if (!_crl) {
78                                         _crls = new ArrayList ();
79                                 }
80                                 if (_crls == null) {
81                                         _crls = BuildCrlsCollection (_storePath);
82                                 }
83                                 return _crls; 
84                         }
85                 }
86
87                 public string Name {
88                         get {
89                                 if (_name == null) {
90                                         int n = _storePath.LastIndexOf (Path.DirectorySeparatorChar);
91                                         _name = _storePath.Substring (n+1);
92                                 }
93                                 return _name;
94                         }
95                 }
96
97                 // methods
98
99                 public void Clear () 
100                 {
101                         if (_certificates != null)
102                                 _certificates.Clear ();
103                         _certificates = null;
104                         if (_crls != null)
105                                 _crls.Clear ();
106                         _crls = null;
107                 }
108
109                 public void Import (X509Certificate certificate) 
110                 {
111                         CheckStore (_storePath, true);
112
113                         string filename = Path.Combine (_storePath, GetUniqueName (certificate));
114                         if (!File.Exists (filename)) {
115                                 using (FileStream fs = File.Create (filename)) {
116                                         byte[] data = certificate.RawData;
117                                         fs.Write (data, 0, data.Length);
118                                         fs.Close ();
119                                 }
120                         }
121 #if !NET_2_1
122                         // Try to save privateKey if available..
123                         CspParameters cspParams = new CspParameters ();
124                         cspParams.KeyContainerName = CryptoConvert.ToHex (certificate.Hash);
125
126                         // Right now this seems to be the best way to know if we should use LM store.. ;)
127                         if (_storePath.StartsWith (X509StoreManager.LocalMachinePath))
128                                 cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
129
130                         ImportPrivateKey (certificate, cspParams);
131 #endif
132                 }
133
134                 public void Import (X509Crl crl) 
135                 {
136                         CheckStore (_storePath, true);
137
138                         string filename = Path.Combine (_storePath, GetUniqueName (crl));
139                         if (!File.Exists (filename)) {
140                                 using (FileStream fs = File.Create (filename)) {
141                                         byte[] data = crl.RawData;
142                                         fs.Write (data, 0, data.Length);
143                                 }
144                         }
145                 }
146
147                 public void Remove (X509Certificate certificate) 
148                 {
149                         string filename = Path.Combine (_storePath, GetUniqueName (certificate));
150                         if (File.Exists (filename)) {
151                                 File.Delete (filename);
152                         }
153                 }
154
155                 public void Remove (X509Crl crl) 
156                 {
157                         string filename = Path.Combine (_storePath, GetUniqueName (crl));
158                         if (File.Exists (filename)) {
159                                 File.Delete (filename);
160                         }
161                 }
162
163                 // private stuff
164
165                 private string GetUniqueName (X509Certificate certificate) 
166                 {
167                         string method;
168                         byte[] name = GetUniqueName (certificate.Extensions);
169                         if (name == null) {
170                                 method = "tbp"; // thumbprint
171                                 name = certificate.Hash;
172                         } else {
173                                 method = "ski";
174                         }
175                         return GetUniqueName (method, name, ".cer");
176                 }
177
178                 private string GetUniqueName (X509Crl crl) 
179                 {
180                         string method;
181                         byte[] name = GetUniqueName (crl.Extensions);
182                         if (name == null) {
183                                 method = "tbp"; // thumbprint
184                                 name = crl.Hash;
185                         } else {
186                                 method = "ski";
187                         }
188                         return GetUniqueName (method, name, ".crl");
189                 }
190
191                 private byte[] GetUniqueName (X509ExtensionCollection extensions) 
192                 {
193                         // We prefer Subject Key Identifier as the unique name
194                         // as it will provide faster lookups
195                         X509Extension ext = extensions ["2.5.29.14"];
196                         if (ext == null)
197                                 return null;
198
199                         SubjectKeyIdentifierExtension ski = new SubjectKeyIdentifierExtension (ext);
200                         return ski.Identifier;
201                 }
202
203                 private string GetUniqueName (string method, byte[] name, string fileExtension) 
204                 {
205                         StringBuilder sb = new StringBuilder (method);
206                         
207                         sb.Append ("-");
208                         foreach (byte b in name) {
209                                 sb.Append (b.ToString ("X2", CultureInfo.InvariantCulture));
210                         }
211                         sb.Append (fileExtension);
212
213                         return sb.ToString ();
214                 }
215
216                 private byte[] Load (string filename) 
217                 {
218                         byte[] data = null;
219                         using (FileStream fs = File.OpenRead (filename)) {
220                                 data = new byte [fs.Length];
221                                 fs.Read (data, 0, data.Length);
222                                 fs.Close ();
223                         }
224                         return data;
225                 }
226
227                 private X509Certificate LoadCertificate (string filename) 
228                 {
229                         byte[] data = Load (filename);
230                         X509Certificate cert = new X509Certificate (data);
231 #if !NET_2_1
232                         // If privateKey it's available, load it too..
233                         CspParameters cspParams = new CspParameters ();
234                         cspParams.KeyContainerName = CryptoConvert.ToHex (cert.Hash);
235                         if (_storePath.StartsWith (X509StoreManager.LocalMachinePath))
236                                 cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
237                         KeyPairPersistence kpp = new KeyPairPersistence (cspParams);
238
239                         if (!kpp.Load ())
240                                 return cert;
241
242                         if (cert.RSA != null)
243                                 cert.RSA = new RSACryptoServiceProvider (cspParams);
244                         else if (cert.DSA != null)
245                                 cert.DSA = new DSACryptoServiceProvider (cspParams);
246 #endif
247                         return cert;
248                 }
249
250                 private X509Crl LoadCrl (string filename) 
251                 {
252                         byte[] data = Load (filename);
253                         X509Crl crl = new X509Crl (data);
254                         return crl;
255                 }
256
257                 private bool CheckStore (string path, bool throwException)
258                 {
259                         try {
260                                 if (Directory.Exists (path))
261                                         return true;
262                                 Directory.CreateDirectory (path);
263                                 return Directory.Exists (path);
264                         }
265                         catch {
266                                 if (throwException)
267                                         throw;
268                                 return false;
269                         }
270                 }
271
272                 private X509CertificateCollection BuildCertificatesCollection (string storeName) 
273                 {
274                         X509CertificateCollection coll = new X509CertificateCollection ();
275                         string path = Path.Combine (_storePath, storeName);
276                         if (!CheckStore (path, false))
277                                 return coll;    // empty collection
278
279                         string[] files = Directory.GetFiles (path, "*.cer");
280                         if ((files != null) && (files.Length > 0)) {
281                                 foreach (string file in files) {
282                                         try {
283                                                 X509Certificate cert = LoadCertificate (file);
284                                                 coll.Add (cert);
285                                         }
286                                         catch {
287                                                 // in case someone is dumb enough
288                                                 // (like me) to include a base64
289                                                 // encoded certs (or other junk 
290                                                 // into the store).
291                                         }
292                                 }
293                         }
294                         return coll;
295                 }
296
297                 private ArrayList BuildCrlsCollection (string storeName) 
298                 {
299                         ArrayList list = new ArrayList ();
300                         string path = Path.Combine (_storePath, storeName);
301                         if (!CheckStore (path, false))
302                                 return list;    // empty list
303
304                         string[] files = Directory.GetFiles (path, "*.crl");
305                         if ((files != null) && (files.Length > 0)) {
306                                 foreach (string file in files) {
307                                         try {
308                                                 X509Crl crl = LoadCrl (file);
309                                                 list.Add (crl);
310                                         }
311                                         catch {
312                                                 // junk catcher
313                                         }
314                                 }
315                         }
316                         return list;
317                 }
318 #if !NET_2_1
319                 private void ImportPrivateKey (X509Certificate certificate, CspParameters cspParams)
320                 {
321                         RSACryptoServiceProvider rsaCsp = certificate.RSA as RSACryptoServiceProvider;
322                         if (rsaCsp != null) {
323                                 if (rsaCsp.PublicOnly)
324                                         return;
325
326                                 RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
327                                 csp.ImportParameters(rsaCsp.ExportParameters(true));
328                                 csp.PersistKeyInCsp = true;
329                                 return;
330                         }
331
332                         RSAManaged rsaMng = certificate.RSA as RSAManaged;
333                         if (rsaMng != null) {
334                                 if (rsaMng.PublicOnly)
335                                         return;
336
337                                 RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
338                                 csp.ImportParameters(rsaMng.ExportParameters(true));
339                                 csp.PersistKeyInCsp = true;
340                                 return;
341                         }
342
343                         DSACryptoServiceProvider dsaCsp = certificate.DSA as DSACryptoServiceProvider;
344                         if (dsaCsp != null) {
345                                 if (dsaCsp.PublicOnly)
346                                         return;
347
348                                 DSACryptoServiceProvider csp = new DSACryptoServiceProvider(cspParams);
349                                 csp.ImportParameters(dsaCsp.ExportParameters(true));
350                                 csp.PersistKeyInCsp = true;
351                         }
352                 }
353 #endif
354         }
355 }