[Mono.Security] Clear Certificates after Import or Remove [part of PR #1004]
[mono.git] / mcs / class / Mono.Security / 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                         /* 
102                          * Both _certificates and _crls extend CollectionBase, whose Clear() method calls OnClear() and 
103                          * OnClearComplete(), which should be overridden in derivative classes. So we should not worry about
104                          * other threads that might be holding references to _certificates or _crls. They should be smart enough
105                          * to handle this gracefully.  And if not, it's their own fault.
106                          */
107                         ClearCertificates ();
108                         ClearCrls ();
109                 }
110
111                 void ClearCertificates()
112                 {
113                         if (_certificates != null)
114                                 _certificates.Clear ();
115                         _certificates = null;
116                 }
117
118                 void ClearCrls ()
119                 {
120                         if (_crls != null)
121                                 _crls.Clear ();
122                         _crls = null;
123                 }
124
125                 public void Import (X509Certificate certificate) 
126                 {
127                         CheckStore (_storePath, true);
128
129                         string filename = Path.Combine (_storePath, GetUniqueName (certificate));
130                         if (!File.Exists (filename)) {
131                                 using (FileStream fs = File.Create (filename)) {
132                                         byte[] data = certificate.RawData;
133                                         fs.Write (data, 0, data.Length);
134                                         fs.Close ();
135                                 }
136                                 ClearCertificates ();   // We have modified the store on disk.  So forget the old state.
137                         }
138 #if !NET_2_1
139                         // Try to save privateKey if available..
140                         CspParameters cspParams = new CspParameters ();
141                         cspParams.KeyContainerName = CryptoConvert.ToHex (certificate.Hash);
142
143                         // Right now this seems to be the best way to know if we should use LM store.. ;)
144                         if (_storePath.StartsWith (X509StoreManager.LocalMachinePath))
145                                 cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
146
147                         ImportPrivateKey (certificate, cspParams);
148 #endif
149                 }
150
151                 public void Import (X509Crl crl) 
152                 {
153                         CheckStore (_storePath, true);
154
155                         string filename = Path.Combine (_storePath, GetUniqueName (crl));
156                         if (!File.Exists (filename)) {
157                                 using (FileStream fs = File.Create (filename)) {
158                                         byte[] data = crl.RawData;
159                                         fs.Write (data, 0, data.Length);
160                                 }
161                                 ClearCrls ();   // We have modified the store on disk.  So forget the old state.
162                         }
163                 }
164
165                 public void Remove (X509Certificate certificate) 
166                 {
167                         string filename = Path.Combine (_storePath, GetUniqueName (certificate));
168                         if (File.Exists (filename)) {
169                                 File.Delete (filename);
170                                 ClearCertificates ();   // We have modified the store on disk.  So forget the old state.
171                         }
172                 }
173
174                 public void Remove (X509Crl crl) 
175                 {
176                         string filename = Path.Combine (_storePath, GetUniqueName (crl));
177                         if (File.Exists (filename)) {
178                                 File.Delete (filename);
179                                 ClearCrls ();   // We have modified the store on disk.  So forget the old state.
180                         }
181                 }
182
183                 // private stuff
184
185                 private string GetUniqueName (X509Certificate certificate) 
186                 {
187                         string method;
188                         byte[] name = GetUniqueName (certificate.Extensions);
189                         if (name == null) {
190                                 method = "tbp"; // thumbprint
191                                 name = certificate.Hash;
192                         } else {
193                                 method = "ski";
194                         }
195                         return GetUniqueName (method, name, ".cer");
196                 }
197
198                 private string GetUniqueName (X509Crl crl) 
199                 {
200                         string method;
201                         byte[] name = GetUniqueName (crl.Extensions);
202                         if (name == null) {
203                                 method = "tbp"; // thumbprint
204                                 name = crl.Hash;
205                         } else {
206                                 method = "ski";
207                         }
208                         return GetUniqueName (method, name, ".crl");
209                 }
210
211                 private byte[] GetUniqueName (X509ExtensionCollection extensions) 
212                 {
213                         // We prefer Subject Key Identifier as the unique name
214                         // as it will provide faster lookups
215                         X509Extension ext = extensions ["2.5.29.14"];
216                         if (ext == null)
217                                 return null;
218
219                         SubjectKeyIdentifierExtension ski = new SubjectKeyIdentifierExtension (ext);
220                         return ski.Identifier;
221                 }
222
223                 private string GetUniqueName (string method, byte[] name, string fileExtension) 
224                 {
225                         StringBuilder sb = new StringBuilder (method);
226                         
227                         sb.Append ("-");
228                         foreach (byte b in name) {
229                                 sb.Append (b.ToString ("X2", CultureInfo.InvariantCulture));
230                         }
231                         sb.Append (fileExtension);
232
233                         return sb.ToString ();
234                 }
235
236                 private byte[] Load (string filename) 
237                 {
238                         byte[] data = null;
239                         using (FileStream fs = File.OpenRead (filename)) {
240                                 data = new byte [fs.Length];
241                                 fs.Read (data, 0, data.Length);
242                                 fs.Close ();
243                         }
244                         return data;
245                 }
246
247                 private X509Certificate LoadCertificate (string filename) 
248                 {
249                         byte[] data = Load (filename);
250                         X509Certificate cert = new X509Certificate (data);
251 #if !NET_2_1
252                         // If privateKey it's available, load it too..
253                         CspParameters cspParams = new CspParameters ();
254                         cspParams.KeyContainerName = CryptoConvert.ToHex (cert.Hash);
255                         if (_storePath.StartsWith (X509StoreManager.LocalMachinePath))
256                                 cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
257                         KeyPairPersistence kpp = new KeyPairPersistence (cspParams);
258
259                         try {
260                                 if (!kpp.Load ())
261                                         return cert;
262                         }
263                         catch {
264                                 return cert;
265                         }
266
267                         if (cert.RSA != null)
268                                 cert.RSA = new RSACryptoServiceProvider (cspParams);
269                         else if (cert.DSA != null)
270                                 cert.DSA = new DSACryptoServiceProvider (cspParams);
271 #endif
272                         return cert;
273                 }
274
275                 private X509Crl LoadCrl (string filename) 
276                 {
277                         byte[] data = Load (filename);
278                         X509Crl crl = new X509Crl (data);
279                         return crl;
280                 }
281
282                 private bool CheckStore (string path, bool throwException)
283                 {
284                         try {
285                                 if (Directory.Exists (path))
286                                         return true;
287                                 Directory.CreateDirectory (path);
288                                 return Directory.Exists (path);
289                         }
290                         catch {
291                                 if (throwException)
292                                         throw;
293                                 return false;
294                         }
295                 }
296
297                 private X509CertificateCollection BuildCertificatesCollection (string storeName) 
298                 {
299                         X509CertificateCollection coll = new X509CertificateCollection ();
300                         string path = Path.Combine (_storePath, storeName);
301                         if (!CheckStore (path, false))
302                                 return coll;    // empty collection
303
304                         string[] files = Directory.GetFiles (path, "*.cer");
305                         if ((files != null) && (files.Length > 0)) {
306                                 foreach (string file in files) {
307                                         try {
308                                                 X509Certificate cert = LoadCertificate (file);
309                                                 coll.Add (cert);
310                                         }
311                                         catch {
312                                                 // in case someone is dumb enough
313                                                 // (like me) to include a base64
314                                                 // encoded certs (or other junk 
315                                                 // into the store).
316                                         }
317                                 }
318                         }
319                         return coll;
320                 }
321
322                 private ArrayList BuildCrlsCollection (string storeName) 
323                 {
324                         ArrayList list = new ArrayList ();
325                         string path = Path.Combine (_storePath, storeName);
326                         if (!CheckStore (path, false))
327                                 return list;    // empty list
328
329                         string[] files = Directory.GetFiles (path, "*.crl");
330                         if ((files != null) && (files.Length > 0)) {
331                                 foreach (string file in files) {
332                                         try {
333                                                 X509Crl crl = LoadCrl (file);
334                                                 list.Add (crl);
335                                         }
336                                         catch {
337                                                 // junk catcher
338                                         }
339                                 }
340                         }
341                         return list;
342                 }
343 #if !NET_2_1
344                 private void ImportPrivateKey (X509Certificate certificate, CspParameters cspParams)
345                 {
346                         RSACryptoServiceProvider rsaCsp = certificate.RSA as RSACryptoServiceProvider;
347                         if (rsaCsp != null) {
348                                 if (rsaCsp.PublicOnly)
349                                         return;
350
351                                 RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
352                                 csp.ImportParameters(rsaCsp.ExportParameters(true));
353                                 csp.PersistKeyInCsp = true;
354                                 return;
355                         }
356
357                         RSAManaged rsaMng = certificate.RSA as RSAManaged;
358                         if (rsaMng != null) {
359                                 if (rsaMng.PublicOnly)
360                                         return;
361
362                                 RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
363                                 csp.ImportParameters(rsaMng.ExportParameters(true));
364                                 csp.PersistKeyInCsp = true;
365                                 return;
366                         }
367
368                         DSACryptoServiceProvider dsaCsp = certificate.DSA as DSACryptoServiceProvider;
369                         if (dsaCsp != null) {
370                                 if (dsaCsp.PublicOnly)
371                                         return;
372
373                                 DSACryptoServiceProvider csp = new DSACryptoServiceProvider(cspParams);
374                                 csp.ImportParameters(dsaCsp.ExportParameters(true));
375                                 csp.PersistKeyInCsp = true;
376                         }
377                 }
378 #endif
379         }
380 }