23ccc8316eb33e91380eb63a3c1a931c42ee7715
[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                                 filename = Path.Combine (_storePath, GetUniqueNameWithSerial (certificate));
132                                 if (!File.Exists (filename)) {
133                                         using (FileStream fs = File.Create (filename)) {
134                                                 byte[] data = certificate.RawData;
135                                                 fs.Write (data, 0, data.Length);
136                                                 fs.Close ();
137                                         }
138                                         ClearCertificates ();   // We have modified the store on disk.  So forget the old state.
139                                 }
140                         } else {
141                                 string newfilename = Path.Combine (_storePath, GetUniqueNameWithSerial (certificate));
142                                 if (GetUniqueNameWithSerial (LoadCertificate (filename)) != GetUniqueNameWithSerial (certificate)) {
143                                         using (FileStream fs = File.Create (newfilename)) {
144                                                 byte[] data = certificate.RawData;
145                                                 fs.Write (data, 0, data.Length);
146                                                 fs.Close ();
147                                         }
148                                         ClearCertificates ();   // We have modified the store on disk.  So forget the old state.
149                                 }
150                         }
151 #if !NET_2_1
152                         // Try to save privateKey if available..
153                         CspParameters cspParams = new CspParameters ();
154                         cspParams.KeyContainerName = CryptoConvert.ToHex (certificate.Hash);
155
156                         // Right now this seems to be the best way to know if we should use LM store.. ;)
157                         if (_storePath.StartsWith (X509StoreManager.LocalMachinePath))
158                                 cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
159
160                         ImportPrivateKey (certificate, cspParams);
161 #endif
162                 }
163
164                 public void Import (X509Crl crl) 
165                 {
166                         CheckStore (_storePath, true);
167
168                         string filename = Path.Combine (_storePath, GetUniqueName (crl));
169                         if (!File.Exists (filename)) {
170                                 using (FileStream fs = File.Create (filename)) {
171                                         byte[] data = crl.RawData;
172                                         fs.Write (data, 0, data.Length);
173                                 }
174                                 ClearCrls ();   // We have modified the store on disk.  So forget the old state.
175                         }
176                 }
177
178                 public void Remove (X509Certificate certificate) 
179                 {
180                         string filename = Path.Combine (_storePath, GetUniqueNameWithSerial (certificate));
181                         if (File.Exists (filename)) {
182                                 File.Delete (filename);
183                                 ClearCertificates ();   // We have modified the store on disk.  So forget the old state.
184                         } else {
185                                 filename = Path.Combine (_storePath, GetUniqueName (certificate));
186                                 if (File.Exists (filename)) {
187                                         File.Delete (filename);
188                                         ClearCertificates ();   // We have modified the store on disk.  So forget the old state.
189                                 }
190                         }
191                 }
192
193                 public void Remove (X509Crl crl) 
194                 {
195                         string filename = Path.Combine (_storePath, GetUniqueName (crl));
196                         if (File.Exists (filename)) {
197                                 File.Delete (filename);
198                                 ClearCrls ();   // We have modified the store on disk.  So forget the old state.
199                         }
200                 }
201
202                 // private stuff
203
204                 private string GetUniqueNameWithSerial (X509Certificate certificate)
205                 {
206                         return GetUniqueName (certificate, certificate.SerialNumber);
207                 }
208
209                 private string GetUniqueName (X509Certificate certificate, byte[] serial = null) 
210                 {
211                         string method;
212                         byte[] name = GetUniqueName (certificate.Extensions, serial);
213                         if (name == null) {
214                                 method = "tbp"; // thumbprint
215                                 name = certificate.Hash;
216                         } else {
217                                 method = "ski";
218                         }
219                         return GetUniqueName (method, name, ".cer");
220                 }
221
222                 private string GetUniqueName (X509Crl crl) 
223                 {
224                         string method;
225                         byte[] name = GetUniqueName (crl.Extensions);
226                         if (name == null) {
227                                 method = "tbp"; // thumbprint
228                                 name = crl.Hash;
229                         } else {
230                                 method = "ski";
231                         }
232                         return GetUniqueName (method, name, ".crl");
233                 }
234
235                 private byte[] GetUniqueName (X509ExtensionCollection extensions, byte[] serial = null) 
236                 {
237                         // We prefer Subject Key Identifier as the unique name
238                         // as it will provide faster lookups
239                         X509Extension ext = extensions ["2.5.29.14"];
240                         if (ext == null)
241                                 return null;
242
243                         SubjectKeyIdentifierExtension ski = new SubjectKeyIdentifierExtension (ext);
244                         if (serial == null) {
245                                 return ski.Identifier;
246                         } else {
247                                 byte[] uniqueWithSerial = new byte[ski.Identifier.Length + serial.Length];
248                                 System.Buffer.BlockCopy (ski.Identifier, 0, uniqueWithSerial, 0, ski.Identifier.Length );
249                                 System.Buffer.BlockCopy (serial, 0, uniqueWithSerial, ski.Identifier.Length, serial.Length );
250                                 return uniqueWithSerial;
251                         }
252                 }
253
254                 private string GetUniqueName (string method, byte[] name, string fileExtension) 
255                 {
256                         StringBuilder sb = new StringBuilder (method);
257                         
258                         sb.Append ("-");
259                         foreach (byte b in name) {
260                                 sb.Append (b.ToString ("X2", CultureInfo.InvariantCulture));
261                         }
262                         sb.Append (fileExtension);
263
264                         return sb.ToString ();
265                 }
266
267                 private byte[] Load (string filename) 
268                 {
269                         byte[] data = null;
270                         using (FileStream fs = File.OpenRead (filename)) {
271                                 data = new byte [fs.Length];
272                                 fs.Read (data, 0, data.Length);
273                                 fs.Close ();
274                         }
275                         return data;
276                 }
277
278                 private X509Certificate LoadCertificate (string filename) 
279                 {
280                         byte[] data = Load (filename);
281                         X509Certificate cert = new X509Certificate (data);
282 #if !NET_2_1
283                         // If privateKey it's available, load it too..
284                         CspParameters cspParams = new CspParameters ();
285                         cspParams.KeyContainerName = CryptoConvert.ToHex (cert.Hash);
286                         if (_storePath.StartsWith (X509StoreManager.LocalMachinePath))
287                                 cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
288                         KeyPairPersistence kpp = new KeyPairPersistence (cspParams);
289
290                         try {
291                                 if (!kpp.Load ())
292                                         return cert;
293                         }
294                         catch {
295                                 return cert;
296                         }
297
298                         if (cert.RSA != null)
299                                 cert.RSA = new RSACryptoServiceProvider (cspParams);
300                         else if (cert.DSA != null)
301                                 cert.DSA = new DSACryptoServiceProvider (cspParams);
302 #endif
303                         return cert;
304                 }
305
306                 private X509Crl LoadCrl (string filename) 
307                 {
308                         byte[] data = Load (filename);
309                         X509Crl crl = new X509Crl (data);
310                         return crl;
311                 }
312
313                 private bool CheckStore (string path, bool throwException)
314                 {
315                         try {
316                                 if (Directory.Exists (path))
317                                         return true;
318                                 Directory.CreateDirectory (path);
319                                 return Directory.Exists (path);
320                         }
321                         catch {
322                                 if (throwException)
323                                         throw;
324                                 return false;
325                         }
326                 }
327
328                 private X509CertificateCollection BuildCertificatesCollection (string storeName) 
329                 {
330                         X509CertificateCollection coll = new X509CertificateCollection ();
331                         string path = Path.Combine (_storePath, storeName);
332                         if (!CheckStore (path, false))
333                                 return coll;    // empty collection
334
335                         string[] files = Directory.GetFiles (path, "*.cer");
336                         if ((files != null) && (files.Length > 0)) {
337                                 foreach (string file in files) {
338                                         try {
339                                                 X509Certificate cert = LoadCertificate (file);
340                                                 coll.Add (cert);
341                                         }
342                                         catch {
343                                                 // in case someone is dumb enough
344                                                 // (like me) to include a base64
345                                                 // encoded certs (or other junk 
346                                                 // into the store).
347                                         }
348                                 }
349                         }
350                         return coll;
351                 }
352
353                 private ArrayList BuildCrlsCollection (string storeName) 
354                 {
355                         ArrayList list = new ArrayList ();
356                         string path = Path.Combine (_storePath, storeName);
357                         if (!CheckStore (path, false))
358                                 return list;    // empty list
359
360                         string[] files = Directory.GetFiles (path, "*.crl");
361                         if ((files != null) && (files.Length > 0)) {
362                                 foreach (string file in files) {
363                                         try {
364                                                 X509Crl crl = LoadCrl (file);
365                                                 list.Add (crl);
366                                         }
367                                         catch {
368                                                 // junk catcher
369                                         }
370                                 }
371                         }
372                         return list;
373                 }
374 #if !NET_2_1
375                 private void ImportPrivateKey (X509Certificate certificate, CspParameters cspParams)
376                 {
377                         RSACryptoServiceProvider rsaCsp = certificate.RSA as RSACryptoServiceProvider;
378                         if (rsaCsp != null) {
379                                 if (rsaCsp.PublicOnly)
380                                         return;
381
382                                 RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
383                                 csp.ImportParameters(rsaCsp.ExportParameters(true));
384                                 csp.PersistKeyInCsp = true;
385                                 return;
386                         }
387
388                         RSAManaged rsaMng = certificate.RSA as RSAManaged;
389                         if (rsaMng != null) {
390                                 if (rsaMng.PublicOnly)
391                                         return;
392
393                                 RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
394                                 csp.ImportParameters(rsaMng.ExportParameters(true));
395                                 csp.PersistKeyInCsp = true;
396                                 return;
397                         }
398
399                         DSACryptoServiceProvider dsaCsp = certificate.DSA as DSACryptoServiceProvider;
400                         if (dsaCsp != null) {
401                                 if (dsaCsp.PublicOnly)
402                                         return;
403
404                                 DSACryptoServiceProvider csp = new DSACryptoServiceProvider(cspParams);
405                                 csp.ImportParameters(dsaCsp.ExportParameters(true));
406                                 csp.PersistKeyInCsp = true;
407                         }
408                 }
409 #endif
410         }
411 }