Initial project's source code check-in.
This commit is contained in:
commit
b03b0b373f
4573 changed files with 981205 additions and 0 deletions
|
@ -0,0 +1,499 @@
|
|||
// Copyright (c) 2011, Outercurve Foundation.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// - Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// - Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// - Neither the name of the Outercurve Foundation nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from this
|
||||
// software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
using Microsoft.Web.Administration;
|
||||
using WebsitePanel.Providers.Common;
|
||||
using WebsitePanel.Server.Utils;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using CERTENROLLLib;
|
||||
using CERTCLIENTLib;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using WebsitePanel.Providers.Web.Iis.Common;
|
||||
using System.Security;
|
||||
using WebsitePanel.Providers.Web.Iis.WebObjects;
|
||||
|
||||
namespace WebsitePanel.Providers.Web.Iis
|
||||
{
|
||||
internal sealed class SSLModuleService : ConfigurationModuleService
|
||||
{
|
||||
public void GenerateCsr(SSLCertificate cert)
|
||||
{
|
||||
// Create all the objects that will be required
|
||||
CX509CertificateRequestPkcs10 pkcs10 = new CX509CertificateRequestPkcs10();
|
||||
CX509PrivateKey privateKey = new CX509PrivateKey();
|
||||
CCspInformation csp = new CCspInformation();
|
||||
CCspInformations csPs = new CCspInformations();
|
||||
CX500DistinguishedName dn = new CX500DistinguishedName();
|
||||
CX509Enrollment enroll = new CX509Enrollment();
|
||||
CObjectIds objectIds = new CObjectIds();
|
||||
CObjectId clientObjectId = new CObjectId();
|
||||
CObjectId serverObjectId = new CObjectId();
|
||||
CX509ExtensionKeyUsage extensionKeyUsage = new CX509ExtensionKeyUsage();
|
||||
CX509ExtensionEnhancedKeyUsage x509ExtensionEnhancedKeyUsage = new CX509ExtensionEnhancedKeyUsage();
|
||||
|
||||
try
|
||||
{
|
||||
// Initialize the csp object using the desired Cryptograhic Service Provider (CSP)
|
||||
csp.InitializeFromName("Microsoft RSA SChannel Cryptographic Provider");
|
||||
// Add this CSP object to the CSP collection object
|
||||
csPs.Add(csp);
|
||||
|
||||
// Provide key container name, key length and key spec to the private key object
|
||||
//objPrivateKey.ContainerName = "AlejaCMa";
|
||||
privateKey.Length = cert.CSRLength;
|
||||
privateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE;
|
||||
privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES;
|
||||
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG | X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
|
||||
privateKey.MachineContext = true;
|
||||
|
||||
// Provide the CSP collection object (in this case containing only 1 CSP object)
|
||||
// to the private key object
|
||||
privateKey.CspInformations = csPs;
|
||||
|
||||
// Create the actual key pair
|
||||
privateKey.Create();
|
||||
|
||||
// Initialize the PKCS#10 certificate request object based on the private key.
|
||||
// Using the context, indicate that this is a user certificate request and don't
|
||||
// provide a template name
|
||||
pkcs10.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
|
||||
|
||||
cert.PrivateKey = privateKey.ToString();
|
||||
// Key Usage Extension
|
||||
extensionKeyUsage.InitializeEncode(
|
||||
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE |
|
||||
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_NON_REPUDIATION_KEY_USAGE |
|
||||
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE |
|
||||
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_DATA_ENCIPHERMENT_KEY_USAGE
|
||||
);
|
||||
|
||||
pkcs10.X509Extensions.Add((CX509Extension)extensionKeyUsage);
|
||||
|
||||
// Enhanced Key Usage Extension
|
||||
clientObjectId.InitializeFromValue("1.3.6.1.5.5.7.3.2");
|
||||
objectIds.Add(clientObjectId);
|
||||
serverObjectId.InitializeFromValue("1.3.6.1.5.5.7.3.1");
|
||||
objectIds.Add(serverObjectId);
|
||||
x509ExtensionEnhancedKeyUsage.InitializeEncode(objectIds);
|
||||
pkcs10.X509Extensions.Add((CX509Extension)x509ExtensionEnhancedKeyUsage);
|
||||
|
||||
// Encode the name in using the Distinguished Name object
|
||||
string request = String.Format(@"CN={0}, O={1}, OU={2}, L={3}, S={4}, C={5}", cert.Hostname, cert.Organisation, cert.OrganisationUnit, cert.City, cert.State, cert.Country);
|
||||
dn.Encode(request, X500NameFlags.XCN_CERT_NAME_STR_NONE);
|
||||
|
||||
// Assing the subject name by using the Distinguished Name object initialized above
|
||||
pkcs10.Subject = dn;
|
||||
|
||||
// Create enrollment request
|
||||
enroll.InitializeFromRequest(pkcs10);
|
||||
|
||||
enroll.CertificateFriendlyName = cert.FriendlyName;
|
||||
|
||||
cert.CSR = enroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64REQUESTHEADER);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.WriteError("Error creating CSR", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public SSLCertificate InstallCertificate(SSLCertificate cert, WebSite website)
|
||||
{
|
||||
CX509Enrollment response = new CX509Enrollment();
|
||||
try
|
||||
{
|
||||
|
||||
response.Initialize(X509CertificateEnrollmentContext.ContextMachine);
|
||||
response.InstallResponse(
|
||||
InstallResponseRestrictionFlags.AllowUntrustedRoot,
|
||||
cert.Certificate, EncodingType.XCN_CRYPT_STRING_BASE64HEADER,
|
||||
null
|
||||
);
|
||||
|
||||
SSLCertificate servercert = (from c in GetServerCertificates()
|
||||
where c.FriendlyName == cert.FriendlyName
|
||||
select c).Single();
|
||||
|
||||
cert.SerialNumber = servercert.SerialNumber;
|
||||
cert.ValidFrom = servercert.ValidFrom;
|
||||
cert.ExpiryDate = servercert.ExpiryDate;
|
||||
cert.Hash = servercert.Hash;
|
||||
cert.DistinguishedName = servercert.DistinguishedName;
|
||||
|
||||
if (cert.IsRenewal && CheckCertificate(website))
|
||||
{
|
||||
DeleteCertificate(GetCurrentSiteCertificate(website), website);
|
||||
}
|
||||
|
||||
AddBinding(cert, website);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
|
||||
Log.WriteError("Error adding SSL certificate", ex);
|
||||
cert.Success = false;
|
||||
}
|
||||
return cert;
|
||||
}
|
||||
|
||||
public List<SSLCertificate> GetServerCertificates()
|
||||
{
|
||||
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
|
||||
//
|
||||
var certificates = new List<SSLCertificate>();
|
||||
//
|
||||
try
|
||||
{
|
||||
store.Open(OpenFlags.ReadOnly);
|
||||
//
|
||||
certificates = (from X509Certificate2 cert in store.Certificates
|
||||
let hostname = cert.GetNameInfo(X509NameType.SimpleName, false)
|
||||
select new SSLCertificate()
|
||||
{
|
||||
FriendlyName = cert.FriendlyName,
|
||||
Hostname = hostname,
|
||||
Hash = cert.GetCertHash(),
|
||||
SerialNumber = cert.SerialNumber,
|
||||
ValidFrom = DateTime.Parse(cert.GetEffectiveDateString()),
|
||||
ExpiryDate = DateTime.Parse(cert.GetExpirationDateString()),
|
||||
DistinguishedName = cert.Subject
|
||||
|
||||
}).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.WriteError(
|
||||
String.Format("SSLModuleService is unable to get certificates from X509Store('{0}', '{1}') and complete GetServerCertificates call", store.Name, store.Location), ex);
|
||||
// Re-throw exception
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
store.Close();
|
||||
}
|
||||
//
|
||||
return certificates;
|
||||
}
|
||||
|
||||
public SSLCertificate InstallPfx(byte[] certificate, string password, WebSite website)
|
||||
{
|
||||
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
|
||||
//
|
||||
SSLCertificate newcert = null, oldcert = null;
|
||||
// Ensure we perform operations safely and preserve the original state during all manipulations
|
||||
if (CheckCertificate(website))
|
||||
oldcert = GetCurrentSiteCertificate(website);
|
||||
//
|
||||
X509Certificate2 x509Cert = new X509Certificate2(certificate, password);
|
||||
|
||||
#region Step 1: Register X.509 certificate in the store
|
||||
// Trying to keep X.509 store open as less as possible
|
||||
try
|
||||
{
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
//
|
||||
store.Add(x509Cert);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.WriteError(String.Format("SSLModuleService could not import PFX into X509Store('{0}', '{1}')", store.Name, store.Location), ex);
|
||||
// Re-throw error
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
store.Close();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Step 2: Instantiate a copy of new X.509 certificate
|
||||
try
|
||||
{
|
||||
//
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
//
|
||||
newcert = new SSLCertificate
|
||||
{
|
||||
Hostname = x509Cert.GetNameInfo(X509NameType.SimpleName, false),
|
||||
FriendlyName = x509Cert.FriendlyName,
|
||||
CSRLength = Convert.ToInt32(x509Cert.PublicKey.Key.KeySize.ToString()),
|
||||
Installed = true,
|
||||
DistinguishedName = x509Cert.Subject,
|
||||
Hash = x509Cert.GetCertHash(),
|
||||
SerialNumber = x509Cert.SerialNumber,
|
||||
ExpiryDate = DateTime.Parse(x509Cert.GetExpirationDateString()),
|
||||
ValidFrom = DateTime.Parse(x509Cert.GetEffectiveDateString()),
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Rollback X.509 store changes
|
||||
store.Remove(x509Cert);
|
||||
// Log error
|
||||
Log.WriteError("SSLModuleService could not instantiate a copy of new X.509 certificate. All previous changes have been rolled back.", ex);
|
||||
// Re-throw
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
store.Close();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Step 3: Remove old certificate from the web site if any
|
||||
try
|
||||
{
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
// Check if certificate already exists, remove it.
|
||||
if (oldcert != null)
|
||||
DeleteCertificate(oldcert, website);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Rollback X.509 store changes
|
||||
store.Remove(x509Cert);
|
||||
// Log the error
|
||||
Log.WriteError(
|
||||
String.Format("SSLModuleService could not remove existing certificate from '{0}' web site. All changes have been rolled back.", website.Name), ex);
|
||||
// Re-throw
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
store.Close();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Step 4: Register new certificate with HTTPS binding on the web site
|
||||
try
|
||||
{
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
//
|
||||
AddBinding(newcert, website);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Install old certificate back if any
|
||||
if (oldcert != null)
|
||||
InstallCertificate(oldcert, website);
|
||||
// Rollback X.509 store changes
|
||||
store.Remove(x509Cert);
|
||||
// Log the error
|
||||
Log.WriteError(
|
||||
String.Format("SSLModuleService could not add new X.509 certificate to '{0}' web site. All changes have been rolled back.", website.Name), ex);
|
||||
// Re-throw
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
store.Close();
|
||||
}
|
||||
#endregion
|
||||
//
|
||||
return newcert;
|
||||
}
|
||||
|
||||
public byte[] ExportPfx(string serialNumber, string password)
|
||||
{
|
||||
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
|
||||
store.Open(OpenFlags.ReadOnly);
|
||||
X509Certificate2 cert = store.Certificates.Find(X509FindType.FindBySerialNumber, serialNumber, false)[0];
|
||||
byte[] exported = cert.Export(X509ContentType.Pfx, password);
|
||||
|
||||
return exported;
|
||||
}
|
||||
|
||||
public void AddBinding(SSLCertificate certificate, WebSite website)
|
||||
{
|
||||
using (ServerManager sm = GetServerManager())
|
||||
{
|
||||
// Not sure why do we need to work with X.509 store here, so commented it out and lets see what happens
|
||||
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
|
||||
store.Open(OpenFlags.ReadOnly);
|
||||
//
|
||||
List<ServerBinding> bindings = new List<ServerBinding>();
|
||||
// Retrieve existing site bindings to figure out what do we have here
|
||||
WebObjectsModuleService webObjSvc = new WebObjectsModuleService();
|
||||
bindings.AddRange(webObjSvc.GetSiteBindings(website.SiteId));
|
||||
// Look for dedicated ip
|
||||
bool dedicatedIp = bindings.Exists(binding => String.IsNullOrEmpty(binding.Host) && binding.IP != "*");
|
||||
//
|
||||
string bindingInformation;
|
||||
//
|
||||
bindingInformation = dedicatedIp ? string.Format("{0}:443:", website.SiteIPAddress)
|
||||
: string.Format("{0}:443:{1}", website.SiteIPAddress, certificate.Hostname);
|
||||
//
|
||||
sm.Sites[website.SiteId].Bindings.Add(bindingInformation, certificate.Hash, store.Name);
|
||||
//
|
||||
store.Close();
|
||||
//
|
||||
sm.CommitChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveBinding(SSLCertificate certificate, WebSite website)
|
||||
{
|
||||
using (ServerManager sm = GetServerManager())
|
||||
{
|
||||
Site site = sm.Sites[website.SiteId];
|
||||
|
||||
Binding sslbind = (from b in site.Bindings
|
||||
where b.Protocol == "https"
|
||||
select b).Single();
|
||||
|
||||
site.Bindings.Remove(sslbind);
|
||||
|
||||
sm.CommitChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public SSLCertificate FindByFriendlyname(string name)
|
||||
{
|
||||
throw new NotImplementedException("Method not implemented");
|
||||
}
|
||||
|
||||
public ResultObject DeleteCertificate(SSLCertificate certificate, WebSite website)
|
||||
{
|
||||
ResultObject result = new ResultObject();
|
||||
|
||||
try
|
||||
{
|
||||
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
|
||||
|
||||
store.Open(OpenFlags.MaxAllowed);
|
||||
|
||||
X509Certificate2 cert =
|
||||
store.Certificates.Find(X509FindType.FindBySerialNumber, certificate.SerialNumber, false)[0];
|
||||
store.Remove(cert);
|
||||
|
||||
store.Close();
|
||||
RemoveBinding(certificate, website);
|
||||
|
||||
result.IsSuccess = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.IsSuccess = false;
|
||||
result.AddError("", ex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public SSLCertificate ImportCertificate(WebSite website)
|
||||
{
|
||||
SSLCertificate certificate = new SSLCertificate { Success = false };
|
||||
try
|
||||
{
|
||||
using (ServerManager sm = GetServerManager())
|
||||
{
|
||||
Site site = sm.Sites[website.SiteId];
|
||||
|
||||
Binding sslbind = (from b in site.Bindings
|
||||
where b.Protocol == "https"
|
||||
select b).Single();
|
||||
|
||||
|
||||
certificate.Hash = sslbind.CertificateHash;
|
||||
|
||||
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
|
||||
|
||||
store.Open(OpenFlags.MaxAllowed);
|
||||
|
||||
|
||||
X509Certificate2 x509Cert = (from X509Certificate2 c in store.Certificates
|
||||
where Convert.ToBase64String(c.GetCertHash()) == Convert.ToBase64String(certificate.Hash)
|
||||
select c).Single();
|
||||
|
||||
store.Close();
|
||||
|
||||
certificate.Hostname = x509Cert.GetNameInfo(X509NameType.SimpleName, false);
|
||||
certificate.FriendlyName = x509Cert.FriendlyName;
|
||||
certificate.CSRLength = Convert.ToInt32(x509Cert.PublicKey.Key.KeySize.ToString());
|
||||
certificate.Installed = true;
|
||||
certificate.DistinguishedName = x509Cert.Subject;
|
||||
certificate.Hash = x509Cert.GetCertHash();
|
||||
certificate.SerialNumber = x509Cert.SerialNumber;
|
||||
certificate.ExpiryDate = DateTime.Parse(x509Cert.GetExpirationDateString());
|
||||
certificate.ValidFrom = DateTime.Parse(x509Cert.GetEffectiveDateString());
|
||||
certificate.Success = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
certificate.Success = false;
|
||||
certificate.Certificate = ex.ToString();
|
||||
}
|
||||
return certificate;
|
||||
}
|
||||
|
||||
//Checks to see if the site has a certificate
|
||||
public bool CheckCertificate(WebSite website)
|
||||
{
|
||||
using (var sm = GetServerManager())
|
||||
{
|
||||
//
|
||||
Site site = sm.Sites[website.SiteId];
|
||||
// Just exit from the loop if https binding found
|
||||
foreach (Binding bind in site.Bindings.Where(bind => bind.Protocol == "https"))
|
||||
return true;
|
||||
//
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public SSLCertificate GetCurrentSiteCertificate(WebSite website)
|
||||
{
|
||||
using (ServerManager sm = GetServerManager())
|
||||
{
|
||||
Site site = sm.Sites[website.SiteId];
|
||||
Binding sslbind = (from b in site.Bindings
|
||||
where b.Protocol == "https"
|
||||
select b).Single();
|
||||
|
||||
byte[] currentHash = sslbind.CertificateHash;
|
||||
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
|
||||
store.Open(OpenFlags.ReadOnly);
|
||||
|
||||
X509Certificate2 oldcertificate = (from X509Certificate2 c in store.Certificates
|
||||
where Convert.ToBase64String(c.GetCertHash()) == Convert.ToBase64String(currentHash)
|
||||
select c).Single();
|
||||
|
||||
store.Close();
|
||||
SSLCertificate certificate = new SSLCertificate();
|
||||
certificate.Hash = oldcertificate.GetCertHash();
|
||||
certificate.SerialNumber = oldcertificate.SerialNumber;
|
||||
return certificate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue