2 // X509Store.cs: Handles a X.509 certificates/CRLs store
5 // Sebastien Pouliot <sebastien@ximian.com>
6 // Pablo Ruiz <pruiz@netway.org>
8 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
9 // (C) 2010 Pablo Ruiz.
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
32 using System.Collections;
33 using System.Globalization;
36 using System.Security.Cryptography;
38 using Mono.Security.Cryptography;
39 using Mono.Security.X509.Extensions;
41 using SSCX = System.Security.Cryptography.X509Certificates;
43 namespace Mono.Security.X509 {
52 private string _storePath;
53 private X509CertificateCollection _certificates;
54 private ArrayList _crls;
56 private bool _newFormat;
59 internal X509Store (string path, bool crl, bool newFormat)
63 _newFormat = newFormat;
68 public X509CertificateCollection Certificates {
70 if (_certificates == null) {
71 _certificates = BuildCertificatesCollection (_storePath);
77 public ArrayList Crls {
79 // CRL aren't applicable to all stores
80 // but returning null is a little rude
82 _crls = new ArrayList ();
85 _crls = BuildCrlsCollection (_storePath);
94 int n = _storePath.LastIndexOf (Path.DirectorySeparatorChar);
95 _name = _storePath.Substring (n+1);
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.
111 ClearCertificates ();
115 void ClearCertificates()
117 if (_certificates != null)
118 _certificates.Clear ();
119 _certificates = null;
129 public void Import (X509Certificate certificate)
131 CheckStore (_storePath, true);
134 ImportNewFormat (certificate);
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);
147 ClearCertificates (); // We have modified the store on disk. So forget the old state.
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);
157 ClearCertificates (); // We have modified the store on disk. So forget the old state.
161 // Try to save privateKey if available..
162 CspParameters cspParams = new CspParameters ();
163 cspParams.KeyContainerName = CryptoConvert.ToHex (certificate.Hash);
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;
169 ImportPrivateKey (certificate, cspParams);
173 public void Import (X509Crl crl)
175 CheckStore (_storePath, true);
178 throw new NotSupportedException ();
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);
186 ClearCrls (); // We have modified the store on disk. So forget the old state.
190 public void Remove (X509Certificate certificate)
193 RemoveNewFormat (certificate);
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.
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.
210 public void Remove (X509Crl crl)
213 throw new NotSupportedException ();
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.
224 void ImportNewFormat (X509Certificate certificate)
227 throw new NotSupportedException ();
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 ();
241 void RemoveNewFormat (X509Certificate certificate)
244 throw new NotSupportedException ();
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 ();
259 private string GetUniqueNameWithSerial (X509Certificate certificate)
261 return GetUniqueName (certificate, certificate.SerialNumber);
264 private string GetUniqueName (X509Certificate certificate, byte[] serial = null)
267 byte[] name = GetUniqueName (certificate.Extensions, serial);
269 method = "tbp"; // thumbprint
270 name = certificate.Hash;
274 return GetUniqueName (method, name, ".cer");
277 private string GetUniqueName (X509Crl crl)
280 byte[] name = GetUniqueName (crl.Extensions);
282 method = "tbp"; // thumbprint
287 return GetUniqueName (method, name, ".crl");
290 private byte[] GetUniqueName (X509ExtensionCollection extensions, byte[] serial = null)
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"];
298 SubjectKeyIdentifierExtension ski = new SubjectKeyIdentifierExtension (ext);
299 if (serial == null) {
300 return ski.Identifier;
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;
309 private string GetUniqueName (string method, byte[] name, string fileExtension)
311 StringBuilder sb = new StringBuilder (method);
314 foreach (byte b in name) {
315 sb.Append (b.ToString ("X2", CultureInfo.InvariantCulture));
317 sb.Append (fileExtension);
319 return sb.ToString ();
322 private byte[] Load (string filename)
325 using (FileStream fs = File.OpenRead (filename)) {
326 data = new byte [fs.Length];
327 fs.Read (data, 0, data.Length);
333 private X509Certificate LoadCertificate (string filename)
335 byte[] data = Load (filename);
336 X509Certificate cert = new X509Certificate (data);
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);
353 if (cert.RSA != null)
354 cert.RSA = new RSACryptoServiceProvider (cspParams);
355 else if (cert.DSA != null)
356 cert.DSA = new DSACryptoServiceProvider (cspParams);
361 private X509Crl LoadCrl (string filename)
363 byte[] data = Load (filename);
364 X509Crl crl = new X509Crl (data);
368 private bool CheckStore (string path, bool throwException)
371 if (Directory.Exists (path))
373 Directory.CreateDirectory (path);
374 return Directory.Exists (path);
383 private X509CertificateCollection BuildCertificatesCollection (string storeName)
385 X509CertificateCollection coll = new X509CertificateCollection ();
386 string path = Path.Combine (_storePath, storeName);
387 if (!CheckStore (path, false))
388 return coll; // empty collection
390 string[] files = Directory.GetFiles (path, _newFormat ? "*.0" : "*.cer");
391 if ((files != null) && (files.Length > 0)) {
392 foreach (string file in files) {
394 X509Certificate cert = LoadCertificate (file);
398 // in case someone is dumb enough
399 // (like me) to include a base64
400 // encoded certs (or other junk
408 private ArrayList BuildCrlsCollection (string storeName)
410 ArrayList list = new ArrayList ();
411 string path = Path.Combine (_storePath, storeName);
412 if (!CheckStore (path, false))
413 return list; // empty list
415 string[] files = Directory.GetFiles (path, "*.crl");
416 if ((files != null) && (files.Length > 0)) {
417 foreach (string file in files) {
419 X509Crl crl = LoadCrl (file);
430 private void ImportPrivateKey (X509Certificate certificate, CspParameters cspParams)
432 RSACryptoServiceProvider rsaCsp = certificate.RSA as RSACryptoServiceProvider;
433 if (rsaCsp != null) {
434 if (rsaCsp.PublicOnly)
437 RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
438 csp.ImportParameters(rsaCsp.ExportParameters(true));
439 csp.PersistKeyInCsp = true;
443 RSAManaged rsaMng = certificate.RSA as RSAManaged;
444 if (rsaMng != null) {
445 if (rsaMng.PublicOnly)
448 RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
449 csp.ImportParameters(rsaMng.ExportParameters(true));
450 csp.PersistKeyInCsp = true;
454 DSACryptoServiceProvider dsaCsp = certificate.DSA as DSACryptoServiceProvider;
455 if (dsaCsp != null) {
456 if (dsaCsp.PublicOnly)
459 DSACryptoServiceProvider csp = new DSACryptoServiceProvider(cspParams);
460 csp.ImportParameters(dsaCsp.ExportParameters(true));
461 csp.PersistKeyInCsp = true;