[Mono.Security] Check NewLocalMachinePath too for deciding if store is machine store...
[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 using SSCX = System.Security.Cryptography.X509Certificates;
42
43 namespace Mono.Security.X509 {
44
45 #if INSIDE_CORLIB
46         internal
47 #else
48         public 
49 #endif
50         class X509Store {
51
52                 private string _storePath;
53                 private X509CertificateCollection _certificates;
54                 private ArrayList _crls;
55                 private bool _crl;
56                 private bool _newFormat;
57                 private string _name;
58
59                 internal X509Store (string path, bool crl, bool newFormat)
60                 {
61                         _storePath = path;
62                         _crl = crl;
63                         _newFormat = newFormat;
64                 }
65
66                 // properties
67
68                 public X509CertificateCollection Certificates {
69                         get { 
70                                 if (_certificates == null) {
71                                         _certificates = BuildCertificatesCollection (_storePath);
72                                 }
73                                 return _certificates; 
74                         }
75                 }
76
77                 public ArrayList Crls {
78                         get {
79                                 // CRL aren't applicable to all stores
80                                 // but returning null is a little rude
81                                 if (!_crl) {
82                                         _crls = new ArrayList ();
83                                 }
84                                 if (_crls == null) {
85                                         _crls = BuildCrlsCollection (_storePath);
86                                 }
87                                 return _crls; 
88                         }
89                 }
90
91                 public string Name {
92                         get {
93                                 if (_name == null) {
94                                         int n = _storePath.LastIndexOf (Path.DirectorySeparatorChar);
95                                         _name = _storePath.Substring (n+1);
96                                 }
97                                 return _name;
98                         }
99                 }
100
101                 // methods
102
103                 public void Clear () 
104                 {
105                         /* 
106                          * Both _certificates and _crls extend CollectionBase, whose Clear() method calls OnClear() and 
107                          * OnClearComplete(), which should be overridden in derivative classes. So we should not worry about
108                          * other threads that might be holding references to _certificates or _crls. They should be smart enough
109                          * to handle this gracefully.  And if not, it's their own fault.
110                          */
111                         ClearCertificates ();
112                         ClearCrls ();
113                 }
114
115                 void ClearCertificates()
116                 {
117                         if (_certificates != null)
118                                 _certificates.Clear ();
119                         _certificates = null;
120                 }
121
122                 void ClearCrls ()
123                 {
124                         if (_crls != null)
125                                 _crls.Clear ();
126                         _crls = null;
127                 }
128
129                 public void Import (X509Certificate certificate) 
130                 {
131                         CheckStore (_storePath, true);
132
133                         if (_newFormat) {
134                                 ImportNewFormat (certificate);
135                                 return;
136                         }
137
138                         string filename = Path.Combine (_storePath, GetUniqueName (certificate));
139                         if (!File.Exists (filename)) {
140                                 filename = Path.Combine (_storePath, GetUniqueNameWithSerial (certificate));
141                                 if (!File.Exists (filename)) {
142                                         using (FileStream fs = File.Create (filename)) {
143                                                 byte[] data = certificate.RawData;
144                                                 fs.Write (data, 0, data.Length);
145                                                 fs.Close ();
146                                         }
147                                         ClearCertificates ();   // We have modified the store on disk.  So forget the old state.
148                                 }
149                         } else {
150                                 string newfilename = Path.Combine (_storePath, GetUniqueNameWithSerial (certificate));
151                                 if (GetUniqueNameWithSerial (LoadCertificate (filename)) != GetUniqueNameWithSerial (certificate)) {
152                                         using (FileStream fs = File.Create (newfilename)) {
153                                                 byte[] data = certificate.RawData;
154                                                 fs.Write (data, 0, data.Length);
155                                                 fs.Close ();
156                                         }
157                                         ClearCertificates ();   // We have modified the store on disk.  So forget the old state.
158                                 }
159                         }
160 #if !MOBILE
161                         // Try to save privateKey if available..
162                         CspParameters cspParams = new CspParameters ();
163                         cspParams.KeyContainerName = CryptoConvert.ToHex (certificate.Hash);
164
165                         // Right now this seems to be the best way to know if we should use LM store.. ;)
166                         if (_storePath.StartsWith (X509StoreManager.LocalMachinePath) || _storePath.StartsWith(X509StoreManager.NewLocalMachinePath))
167                                 cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
168
169                         ImportPrivateKey (certificate, cspParams);
170 #endif
171                 }
172
173                 public void Import (X509Crl crl) 
174                 {
175                         CheckStore (_storePath, true);
176
177                         if (_newFormat)
178                                 throw new NotSupportedException ();
179
180                         string filename = Path.Combine (_storePath, GetUniqueName (crl));
181                         if (!File.Exists (filename)) {
182                                 using (FileStream fs = File.Create (filename)) {
183                                         byte[] data = crl.RawData;
184                                         fs.Write (data, 0, data.Length);
185                                 }
186                                 ClearCrls ();   // We have modified the store on disk.  So forget the old state.
187                         }
188                 }
189
190                 public void Remove (X509Certificate certificate) 
191                 {
192                         if (_newFormat) {
193                                 RemoveNewFormat (certificate);
194                                 return;
195                         }
196
197                         string filename = Path.Combine (_storePath, GetUniqueNameWithSerial (certificate));
198                         if (File.Exists (filename)) {
199                                 File.Delete (filename);
200                                 ClearCertificates ();   // We have modified the store on disk.  So forget the old state.
201                         } else {
202                                 filename = Path.Combine (_storePath, GetUniqueName (certificate));
203                                 if (File.Exists (filename)) {
204                                         File.Delete (filename);
205                                         ClearCertificates ();   // We have modified the store on disk.  So forget the old state.
206                                 }
207                         }
208                 }
209
210                 public void Remove (X509Crl crl) 
211                 {
212                         if (_newFormat)
213                                 throw new NotSupportedException ();
214
215                         string filename = Path.Combine (_storePath, GetUniqueName (crl));
216                         if (File.Exists (filename)) {
217                                 File.Delete (filename);
218                                 ClearCrls ();   // We have modified the store on disk.  So forget the old state.
219                         }
220                 }
221
222                 // new format
223
224                 void ImportNewFormat (X509Certificate certificate)
225                 {
226 #if INSIDE_CORLIB
227                         throw new NotSupportedException ();
228 #else
229                         using (var sscxCert = new SSCX.X509Certificate (certificate.RawData)) {
230                                 var hash = SSCX.X509Helper2.GetSubjectNameHash (sscxCert);
231                                 var filename = Path.Combine (_storePath, string.Format ("{0:x8}.0", hash));
232                                 if (!File.Exists (filename)) {
233                                         using (FileStream fs = File.Create (filename))
234                                                 SSCX.X509Helper2.ExportAsPEM (sscxCert, fs, true);
235                                         ClearCertificates ();
236                                 }
237                         }
238 #endif
239                 }
240
241                 void RemoveNewFormat (X509Certificate certificate)
242                 {
243 #if INSIDE_CORLIB
244                         throw new NotSupportedException ();
245 #else
246                         using (var sscxCert = new SSCX.X509Certificate (certificate.RawData)) {
247                                 var hash = SSCX.X509Helper2.GetSubjectNameHash (sscxCert);
248                                 var filename = Path.Combine (_storePath, string.Format ("{0:x8}.0", hash));
249                                 if (File.Exists (filename)) {
250                                         File.Delete (filename);
251                                         ClearCertificates ();
252                                 }
253                         }
254 #endif
255                 }
256
257                 // private stuff
258
259                 private string GetUniqueNameWithSerial (X509Certificate certificate)
260                 {
261                         return GetUniqueName (certificate, certificate.SerialNumber);
262                 }
263
264                 private string GetUniqueName (X509Certificate certificate, byte[] serial = null) 
265                 {
266                         string method;
267                         byte[] name = GetUniqueName (certificate.Extensions, serial);
268                         if (name == null) {
269                                 method = "tbp"; // thumbprint
270                                 name = certificate.Hash;
271                         } else {
272                                 method = "ski";
273                         }
274                         return GetUniqueName (method, name, ".cer");
275                 }
276
277                 private string GetUniqueName (X509Crl crl) 
278                 {
279                         string method;
280                         byte[] name = GetUniqueName (crl.Extensions);
281                         if (name == null) {
282                                 method = "tbp"; // thumbprint
283                                 name = crl.Hash;
284                         } else {
285                                 method = "ski";
286                         }
287                         return GetUniqueName (method, name, ".crl");
288                 }
289
290                 private byte[] GetUniqueName (X509ExtensionCollection extensions, byte[] serial = null) 
291                 {
292                         // We prefer Subject Key Identifier as the unique name
293                         // as it will provide faster lookups
294                         X509Extension ext = extensions ["2.5.29.14"];
295                         if (ext == null)
296                                 return null;
297
298                         SubjectKeyIdentifierExtension ski = new SubjectKeyIdentifierExtension (ext);
299                         if (serial == null) {
300                                 return ski.Identifier;
301                         } else {
302                                 byte[] uniqueWithSerial = new byte[ski.Identifier.Length + serial.Length];
303                                 System.Buffer.BlockCopy (ski.Identifier, 0, uniqueWithSerial, 0, ski.Identifier.Length );
304                                 System.Buffer.BlockCopy (serial, 0, uniqueWithSerial, ski.Identifier.Length, serial.Length );
305                                 return uniqueWithSerial;
306                         }
307                 }
308
309                 private string GetUniqueName (string method, byte[] name, string fileExtension) 
310                 {
311                         StringBuilder sb = new StringBuilder (method);
312                         
313                         sb.Append ("-");
314                         foreach (byte b in name) {
315                                 sb.Append (b.ToString ("X2", CultureInfo.InvariantCulture));
316                         }
317                         sb.Append (fileExtension);
318
319                         return sb.ToString ();
320                 }
321
322                 private byte[] Load (string filename) 
323                 {
324                         byte[] data = null;
325                         using (FileStream fs = File.OpenRead (filename)) {
326                                 data = new byte [fs.Length];
327                                 fs.Read (data, 0, data.Length);
328                                 fs.Close ();
329                         }
330                         return data;
331                 }
332
333                 private X509Certificate LoadCertificate (string filename) 
334                 {
335                         byte[] data = Load (filename);
336                         X509Certificate cert = new X509Certificate (data);
337 #if !MOBILE
338                         // If privateKey it's available, load it too..
339                         CspParameters cspParams = new CspParameters ();
340                         cspParams.KeyContainerName = CryptoConvert.ToHex (cert.Hash);
341                         if (_storePath.StartsWith (X509StoreManager.LocalMachinePath) || _storePath.StartsWith(X509StoreManager.NewLocalMachinePath))
342                                 cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
343                         KeyPairPersistence kpp = new KeyPairPersistence (cspParams);
344
345                         try {
346                                 if (!kpp.Load ())
347                                         return cert;
348                         }
349                         catch {
350                                 return cert;
351                         }
352
353                         if (cert.RSA != null)
354                                 cert.RSA = new RSACryptoServiceProvider (cspParams);
355                         else if (cert.DSA != null)
356                                 cert.DSA = new DSACryptoServiceProvider (cspParams);
357 #endif
358                         return cert;
359                 }
360
361                 private X509Crl LoadCrl (string filename) 
362                 {
363                         byte[] data = Load (filename);
364                         X509Crl crl = new X509Crl (data);
365                         return crl;
366                 }
367
368                 private bool CheckStore (string path, bool throwException)
369                 {
370                         try {
371                                 if (Directory.Exists (path))
372                                         return true;
373                                 Directory.CreateDirectory (path);
374                                 return Directory.Exists (path);
375                         }
376                         catch {
377                                 if (throwException)
378                                         throw;
379                                 return false;
380                         }
381                 }
382
383                 private X509CertificateCollection BuildCertificatesCollection (string storeName) 
384                 {
385                         X509CertificateCollection coll = new X509CertificateCollection ();
386                         string path = Path.Combine (_storePath, storeName);
387                         if (!CheckStore (path, false))
388                                 return coll;    // empty collection
389
390                         string[] files = Directory.GetFiles (path, _newFormat ? "*.0" : "*.cer");
391                         if ((files != null) && (files.Length > 0)) {
392                                 foreach (string file in files) {
393                                         try {
394                                                 X509Certificate cert = LoadCertificate (file);
395                                                 coll.Add (cert);
396                                         }
397                                         catch {
398                                                 // in case someone is dumb enough
399                                                 // (like me) to include a base64
400                                                 // encoded certs (or other junk 
401                                                 // into the store).
402                                         }
403                                 }
404                         }
405                         return coll;
406                 }
407
408                 private ArrayList BuildCrlsCollection (string storeName) 
409                 {
410                         ArrayList list = new ArrayList ();
411                         string path = Path.Combine (_storePath, storeName);
412                         if (!CheckStore (path, false))
413                                 return list;    // empty list
414
415                         string[] files = Directory.GetFiles (path, "*.crl");
416                         if ((files != null) && (files.Length > 0)) {
417                                 foreach (string file in files) {
418                                         try {
419                                                 X509Crl crl = LoadCrl (file);
420                                                 list.Add (crl);
421                                         }
422                                         catch {
423                                                 // junk catcher
424                                         }
425                                 }
426                         }
427                         return list;
428                 }
429 #if !MOBILE
430                 private void ImportPrivateKey (X509Certificate certificate, CspParameters cspParams)
431                 {
432                         RSACryptoServiceProvider rsaCsp = certificate.RSA as RSACryptoServiceProvider;
433                         if (rsaCsp != null) {
434                                 if (rsaCsp.PublicOnly)
435                                         return;
436
437                                 RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
438                                 csp.ImportParameters(rsaCsp.ExportParameters(true));
439                                 csp.PersistKeyInCsp = true;
440                                 return;
441                         }
442
443                         RSAManaged rsaMng = certificate.RSA as RSAManaged;
444                         if (rsaMng != null) {
445                                 if (rsaMng.PublicOnly)
446                                         return;
447
448                                 RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
449                                 csp.ImportParameters(rsaMng.ExportParameters(true));
450                                 csp.PersistKeyInCsp = true;
451                                 return;
452                         }
453
454                         DSACryptoServiceProvider dsaCsp = certificate.DSA as DSACryptoServiceProvider;
455                         if (dsaCsp != null) {
456                                 if (dsaCsp.PublicOnly)
457                                         return;
458
459                                 DSACryptoServiceProvider csp = new DSACryptoServiceProvider(cspParams);
460                                 csp.ImportParameters(dsaCsp.ExportParameters(true));
461                                 csp.PersistKeyInCsp = true;
462                         }
463                 }
464 #endif
465         }
466 }