a632333cc7
Former-commit-id: 6427cc082e74df30afc535fd906a3494b74b0817
412 lines
11 KiB
C#
412 lines
11 KiB
C#
//
|
|
// X509Store.cs: Handles a X.509 certificates/CRLs store
|
|
//
|
|
// Author:
|
|
// Sebastien Pouliot <sebastien@ximian.com>
|
|
// Pablo Ruiz <pruiz@netway.org>
|
|
//
|
|
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
|
|
// (C) 2010 Pablo Ruiz.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Security.Cryptography;
|
|
|
|
using Mono.Security.Cryptography;
|
|
using Mono.Security.X509.Extensions;
|
|
|
|
namespace Mono.Security.X509 {
|
|
|
|
#if INSIDE_CORLIB
|
|
internal
|
|
#else
|
|
public
|
|
#endif
|
|
class X509Store {
|
|
|
|
private string _storePath;
|
|
private X509CertificateCollection _certificates;
|
|
private ArrayList _crls;
|
|
private bool _crl;
|
|
private string _name;
|
|
|
|
internal X509Store (string path, bool crl)
|
|
{
|
|
_storePath = path;
|
|
_crl = crl;
|
|
}
|
|
|
|
// properties
|
|
|
|
public X509CertificateCollection Certificates {
|
|
get {
|
|
if (_certificates == null) {
|
|
_certificates = BuildCertificatesCollection (_storePath);
|
|
}
|
|
return _certificates;
|
|
}
|
|
}
|
|
|
|
public ArrayList Crls {
|
|
get {
|
|
// CRL aren't applicable to all stores
|
|
// but returning null is a little rude
|
|
if (!_crl) {
|
|
_crls = new ArrayList ();
|
|
}
|
|
if (_crls == null) {
|
|
_crls = BuildCrlsCollection (_storePath);
|
|
}
|
|
return _crls;
|
|
}
|
|
}
|
|
|
|
public string Name {
|
|
get {
|
|
if (_name == null) {
|
|
int n = _storePath.LastIndexOf (Path.DirectorySeparatorChar);
|
|
_name = _storePath.Substring (n+1);
|
|
}
|
|
return _name;
|
|
}
|
|
}
|
|
|
|
// methods
|
|
|
|
public void Clear ()
|
|
{
|
|
/*
|
|
* Both _certificates and _crls extend CollectionBase, whose Clear() method calls OnClear() and
|
|
* OnClearComplete(), which should be overridden in derivative classes. So we should not worry about
|
|
* other threads that might be holding references to _certificates or _crls. They should be smart enough
|
|
* to handle this gracefully. And if not, it's their own fault.
|
|
*/
|
|
ClearCertificates ();
|
|
ClearCrls ();
|
|
}
|
|
|
|
void ClearCertificates()
|
|
{
|
|
if (_certificates != null)
|
|
_certificates.Clear ();
|
|
_certificates = null;
|
|
}
|
|
|
|
void ClearCrls ()
|
|
{
|
|
if (_crls != null)
|
|
_crls.Clear ();
|
|
_crls = null;
|
|
}
|
|
|
|
public void Import (X509Certificate certificate)
|
|
{
|
|
CheckStore (_storePath, true);
|
|
|
|
string filename = Path.Combine (_storePath, GetUniqueName (certificate));
|
|
if (!File.Exists (filename)) {
|
|
filename = Path.Combine (_storePath, GetUniqueNameWithSerial (certificate));
|
|
if (!File.Exists (filename)) {
|
|
using (FileStream fs = File.Create (filename)) {
|
|
byte[] data = certificate.RawData;
|
|
fs.Write (data, 0, data.Length);
|
|
fs.Close ();
|
|
}
|
|
ClearCertificates (); // We have modified the store on disk. So forget the old state.
|
|
}
|
|
} else {
|
|
string newfilename = Path.Combine (_storePath, GetUniqueNameWithSerial (certificate));
|
|
if (GetUniqueNameWithSerial (LoadCertificate (filename)) != GetUniqueNameWithSerial (certificate)) {
|
|
using (FileStream fs = File.Create (newfilename)) {
|
|
byte[] data = certificate.RawData;
|
|
fs.Write (data, 0, data.Length);
|
|
fs.Close ();
|
|
}
|
|
ClearCertificates (); // We have modified the store on disk. So forget the old state.
|
|
}
|
|
}
|
|
#if !NET_2_1
|
|
// Try to save privateKey if available..
|
|
CspParameters cspParams = new CspParameters ();
|
|
cspParams.KeyContainerName = CryptoConvert.ToHex (certificate.Hash);
|
|
|
|
// Right now this seems to be the best way to know if we should use LM store.. ;)
|
|
if (_storePath.StartsWith (X509StoreManager.LocalMachinePath))
|
|
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
|
|
|
|
ImportPrivateKey (certificate, cspParams);
|
|
#endif
|
|
}
|
|
|
|
public void Import (X509Crl crl)
|
|
{
|
|
CheckStore (_storePath, true);
|
|
|
|
string filename = Path.Combine (_storePath, GetUniqueName (crl));
|
|
if (!File.Exists (filename)) {
|
|
using (FileStream fs = File.Create (filename)) {
|
|
byte[] data = crl.RawData;
|
|
fs.Write (data, 0, data.Length);
|
|
}
|
|
ClearCrls (); // We have modified the store on disk. So forget the old state.
|
|
}
|
|
}
|
|
|
|
public void Remove (X509Certificate certificate)
|
|
{
|
|
string filename = Path.Combine (_storePath, GetUniqueNameWithSerial (certificate));
|
|
if (File.Exists (filename)) {
|
|
File.Delete (filename);
|
|
ClearCertificates (); // We have modified the store on disk. So forget the old state.
|
|
} else {
|
|
filename = Path.Combine (_storePath, GetUniqueName (certificate));
|
|
if (File.Exists (filename)) {
|
|
File.Delete (filename);
|
|
ClearCertificates (); // We have modified the store on disk. So forget the old state.
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Remove (X509Crl crl)
|
|
{
|
|
string filename = Path.Combine (_storePath, GetUniqueName (crl));
|
|
if (File.Exists (filename)) {
|
|
File.Delete (filename);
|
|
ClearCrls (); // We have modified the store on disk. So forget the old state.
|
|
}
|
|
}
|
|
|
|
// private stuff
|
|
|
|
private string GetUniqueNameWithSerial (X509Certificate certificate)
|
|
{
|
|
return GetUniqueName (certificate, certificate.SerialNumber);
|
|
}
|
|
|
|
private string GetUniqueName (X509Certificate certificate, byte[] serial = null)
|
|
{
|
|
string method;
|
|
byte[] name = GetUniqueName (certificate.Extensions, serial);
|
|
if (name == null) {
|
|
method = "tbp"; // thumbprint
|
|
name = certificate.Hash;
|
|
} else {
|
|
method = "ski";
|
|
}
|
|
return GetUniqueName (method, name, ".cer");
|
|
}
|
|
|
|
private string GetUniqueName (X509Crl crl)
|
|
{
|
|
string method;
|
|
byte[] name = GetUniqueName (crl.Extensions);
|
|
if (name == null) {
|
|
method = "tbp"; // thumbprint
|
|
name = crl.Hash;
|
|
} else {
|
|
method = "ski";
|
|
}
|
|
return GetUniqueName (method, name, ".crl");
|
|
}
|
|
|
|
private byte[] GetUniqueName (X509ExtensionCollection extensions, byte[] serial = null)
|
|
{
|
|
// We prefer Subject Key Identifier as the unique name
|
|
// as it will provide faster lookups
|
|
X509Extension ext = extensions ["2.5.29.14"];
|
|
if (ext == null)
|
|
return null;
|
|
|
|
SubjectKeyIdentifierExtension ski = new SubjectKeyIdentifierExtension (ext);
|
|
if (serial == null) {
|
|
return ski.Identifier;
|
|
} else {
|
|
byte[] uniqueWithSerial = new byte[ski.Identifier.Length + serial.Length];
|
|
System.Buffer.BlockCopy (ski.Identifier, 0, uniqueWithSerial, 0, ski.Identifier.Length );
|
|
System.Buffer.BlockCopy (serial, 0, uniqueWithSerial, ski.Identifier.Length, serial.Length );
|
|
return uniqueWithSerial;
|
|
}
|
|
}
|
|
|
|
private string GetUniqueName (string method, byte[] name, string fileExtension)
|
|
{
|
|
StringBuilder sb = new StringBuilder (method);
|
|
|
|
sb.Append ("-");
|
|
foreach (byte b in name) {
|
|
sb.Append (b.ToString ("X2", CultureInfo.InvariantCulture));
|
|
}
|
|
sb.Append (fileExtension);
|
|
|
|
return sb.ToString ();
|
|
}
|
|
|
|
private byte[] Load (string filename)
|
|
{
|
|
byte[] data = null;
|
|
using (FileStream fs = File.OpenRead (filename)) {
|
|
data = new byte [fs.Length];
|
|
fs.Read (data, 0, data.Length);
|
|
fs.Close ();
|
|
}
|
|
return data;
|
|
}
|
|
|
|
private X509Certificate LoadCertificate (string filename)
|
|
{
|
|
byte[] data = Load (filename);
|
|
X509Certificate cert = new X509Certificate (data);
|
|
#if !NET_2_1
|
|
// If privateKey it's available, load it too..
|
|
CspParameters cspParams = new CspParameters ();
|
|
cspParams.KeyContainerName = CryptoConvert.ToHex (cert.Hash);
|
|
if (_storePath.StartsWith (X509StoreManager.LocalMachinePath))
|
|
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
|
|
KeyPairPersistence kpp = new KeyPairPersistence (cspParams);
|
|
|
|
try {
|
|
if (!kpp.Load ())
|
|
return cert;
|
|
}
|
|
catch {
|
|
return cert;
|
|
}
|
|
|
|
if (cert.RSA != null)
|
|
cert.RSA = new RSACryptoServiceProvider (cspParams);
|
|
else if (cert.DSA != null)
|
|
cert.DSA = new DSACryptoServiceProvider (cspParams);
|
|
#endif
|
|
return cert;
|
|
}
|
|
|
|
private X509Crl LoadCrl (string filename)
|
|
{
|
|
byte[] data = Load (filename);
|
|
X509Crl crl = new X509Crl (data);
|
|
return crl;
|
|
}
|
|
|
|
private bool CheckStore (string path, bool throwException)
|
|
{
|
|
try {
|
|
if (Directory.Exists (path))
|
|
return true;
|
|
Directory.CreateDirectory (path);
|
|
return Directory.Exists (path);
|
|
}
|
|
catch {
|
|
if (throwException)
|
|
throw;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private X509CertificateCollection BuildCertificatesCollection (string storeName)
|
|
{
|
|
X509CertificateCollection coll = new X509CertificateCollection ();
|
|
string path = Path.Combine (_storePath, storeName);
|
|
if (!CheckStore (path, false))
|
|
return coll; // empty collection
|
|
|
|
string[] files = Directory.GetFiles (path, "*.cer");
|
|
if ((files != null) && (files.Length > 0)) {
|
|
foreach (string file in files) {
|
|
try {
|
|
X509Certificate cert = LoadCertificate (file);
|
|
coll.Add (cert);
|
|
}
|
|
catch {
|
|
// in case someone is dumb enough
|
|
// (like me) to include a base64
|
|
// encoded certs (or other junk
|
|
// into the store).
|
|
}
|
|
}
|
|
}
|
|
return coll;
|
|
}
|
|
|
|
private ArrayList BuildCrlsCollection (string storeName)
|
|
{
|
|
ArrayList list = new ArrayList ();
|
|
string path = Path.Combine (_storePath, storeName);
|
|
if (!CheckStore (path, false))
|
|
return list; // empty list
|
|
|
|
string[] files = Directory.GetFiles (path, "*.crl");
|
|
if ((files != null) && (files.Length > 0)) {
|
|
foreach (string file in files) {
|
|
try {
|
|
X509Crl crl = LoadCrl (file);
|
|
list.Add (crl);
|
|
}
|
|
catch {
|
|
// junk catcher
|
|
}
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
#if !NET_2_1
|
|
private void ImportPrivateKey (X509Certificate certificate, CspParameters cspParams)
|
|
{
|
|
RSACryptoServiceProvider rsaCsp = certificate.RSA as RSACryptoServiceProvider;
|
|
if (rsaCsp != null) {
|
|
if (rsaCsp.PublicOnly)
|
|
return;
|
|
|
|
RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
|
|
csp.ImportParameters(rsaCsp.ExportParameters(true));
|
|
csp.PersistKeyInCsp = true;
|
|
return;
|
|
}
|
|
|
|
RSAManaged rsaMng = certificate.RSA as RSAManaged;
|
|
if (rsaMng != null) {
|
|
if (rsaMng.PublicOnly)
|
|
return;
|
|
|
|
RSACryptoServiceProvider csp = new RSACryptoServiceProvider(cspParams);
|
|
csp.ImportParameters(rsaMng.ExportParameters(true));
|
|
csp.PersistKeyInCsp = true;
|
|
return;
|
|
}
|
|
|
|
DSACryptoServiceProvider dsaCsp = certificate.DSA as DSACryptoServiceProvider;
|
|
if (dsaCsp != null) {
|
|
if (dsaCsp.PublicOnly)
|
|
return;
|
|
|
|
DSACryptoServiceProvider csp = new DSACryptoServiceProvider(cspParams);
|
|
csp.ImportParameters(dsaCsp.ExportParameters(true));
|
|
csp.PersistKeyInCsp = true;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|