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 namespace Mono.Security.X509 {
50 private string _storePath;
51 private X509CertificateCollection _certificates;
52 private ArrayList _crls;
56 internal X509Store (string path, bool crl)
64 public X509CertificateCollection Certificates {
66 if (_certificates == null) {
67 _certificates = BuildCertificatesCollection (_storePath);
73 public ArrayList Crls {
75 // CRL aren't applicable to all stores
76 // but returning null is a little rude
78 _crls = new ArrayList ();
81 _crls = BuildCrlsCollection (_storePath);
90 int n = _storePath.LastIndexOf (Path.DirectorySeparatorChar);
91 _name = _storePath.Substring (n+1);
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.
107 ClearCertificates ();
111 void ClearCertificates()
113 if (_certificates != null)
114 _certificates.Clear ();
115 _certificates = null;
125 public void Import (X509Certificate certificate)
127 CheckStore (_storePath, true);
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);
138 ClearCertificates (); // We have modified the store on disk. So forget the old state.
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);
148 ClearCertificates (); // We have modified the store on disk. So forget the old state.
152 // Try to save privateKey if available..
153 CspParameters cspParams = new CspParameters ();
154 cspParams.KeyContainerName = CryptoConvert.ToHex (certificate.Hash);
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;
160 ImportPrivateKey (certificate, cspParams);
164 public void Import (X509Crl crl)
166 CheckStore (_storePath, true);
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);
174 ClearCrls (); // We have modified the store on disk. So forget the old state.
178 public void Remove (X509Certificate certificate)
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.
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.
193 public void Remove (X509Crl crl)
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.
204 private string GetUniqueNameWithSerial (X509Certificate certificate)
206 return GetUniqueName (certificate, certificate.SerialNumber);
209 private string GetUniqueName (X509Certificate certificate, byte[] serial = null)
212 byte[] name = GetUniqueName (certificate.Extensions, serial);
214 method = "tbp"; // thumbprint
215 name = certificate.Hash;
219 return GetUniqueName (method, name, ".cer");
222 private string GetUniqueName (X509Crl crl)
225 byte[] name = GetUniqueName (crl.Extensions);
227 method = "tbp"; // thumbprint
232 return GetUniqueName (method, name, ".crl");
235 private byte[] GetUniqueName (X509ExtensionCollection extensions, byte[] serial = null)
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"];
243 SubjectKeyIdentifierExtension ski = new SubjectKeyIdentifierExtension (ext);
244 if (serial == null) {
245 return ski.Identifier;
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;
254 private string GetUniqueName (string method, byte[] name, string fileExtension)
256 StringBuilder sb = new StringBuilder (method);
259 foreach (byte b in name) {
260 sb.Append (b.ToString ("X2", CultureInfo.InvariantCulture));
262 sb.Append (fileExtension);
264 return sb.ToString ();
267 private byte[] Load (string filename)
270 using (FileStream fs = File.OpenRead (filename)) {
271 data = new byte [fs.Length];
272 fs.Read (data, 0, data.Length);
278 private X509Certificate LoadCertificate (string filename)
280 byte[] data = Load (filename);
281 X509Certificate cert = new X509Certificate (data);
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);
298 if (cert.RSA != null)
299 cert.RSA = new RSACryptoServiceProvider (cspParams);
300 else if (cert.DSA != null)
301 cert.DSA = new DSACryptoServiceProvider (cspParams);
306 private X509Crl LoadCrl (string filename)
308 byte[] data = Load (filename);
309 X509Crl crl = new X509Crl (data);
313 private bool CheckStore (string path, bool throwException)
316 if (Directory.Exists (path))
318 Directory.CreateDirectory (path);
319 return Directory.Exists (path);
328 private X509CertificateCollection BuildCertificatesCollection (string storeName)
330 X509CertificateCollection coll = new X509CertificateCollection ();
331 string path = Path.Combine (_storePath, storeName);
332 if (!CheckStore (path, false))
333 return coll; // empty collection
335 string[] files = Directory.GetFiles (path, "*.cer");
336 if ((files != null) && (files.Length > 0)) {
337 foreach (string file in files) {
339 X509Certificate cert = LoadCertificate (file);
343 // in case someone is dumb enough
344 // (like me) to include a base64
345 // encoded certs (or other junk
353 private ArrayList BuildCrlsCollection (string storeName)
355 ArrayList list = new ArrayList ();
356 string path = Path.Combine (_storePath, storeName);
357 if (!CheckStore (path, false))
358 return list; // empty list
360 string[] files = Directory.GetFiles (path, "*.crl");
361 if ((files != null) && (files.Length > 0)) {
362 foreach (string file in files) {
364 X509Crl crl = LoadCrl (file);
375 private void ImportPrivateKey (X509Certificate certificate, CspParameters cspParams)
377 RSACryptoServiceProvider rsaCsp = certificate.RSA as RSACryptoServiceProvider;
378 if (rsaCsp != null) {
379 if (rsaCsp.PublicOnly)
382 RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
383 csp.ImportParameters(rsaCsp.ExportParameters(true));
384 csp.PersistKeyInCsp = true;
388 RSAManaged rsaMng = certificate.RSA as RSAManaged;
389 if (rsaMng != null) {
390 if (rsaMng.PublicOnly)
393 RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
394 csp.ImportParameters(rsaMng.ExportParameters(true));
395 csp.PersistKeyInCsp = true;
399 DSACryptoServiceProvider dsaCsp = certificate.DSA as DSACryptoServiceProvider;
400 if (dsaCsp != null) {
401 if (dsaCsp.PublicOnly)
404 DSACryptoServiceProvider csp = new DSACryptoServiceProvider(cspParams);
405 csp.ImportParameters(dsaCsp.ExportParameters(true));
406 csp.PersistKeyInCsp = true;