|
|
|
@ -0,0 +1,263 @@
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using System.Security;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using MediaBrowser.Model.Logging;
|
|
|
|
|
|
|
|
|
|
namespace MediaBrowser.ServerApplication.Networking
|
|
|
|
|
{
|
|
|
|
|
// Copied from: http://blogs.msdn.com/b/dcook/archive/2014/05/16/9143036.aspx
|
|
|
|
|
// In case anybody is interested, source code is attached and is free for use by anybody as long as you don't hold me or Microsoft liable for it --
|
|
|
|
|
// I have no idea whether this is actually the right or best way to do this. Give it the X500 distinguished name, validity start and end dates,
|
|
|
|
|
// and an optional password for encrypting the key data, and it will give you the PFX file data. Let me know if you find any bugs or have any suggestions.
|
|
|
|
|
internal class CertificateGenerator
|
|
|
|
|
{
|
|
|
|
|
internal static void CreateSelfSignCertificatePfx(
|
|
|
|
|
string fileName,
|
|
|
|
|
string hostname,
|
|
|
|
|
ILogger logger)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(fileName))
|
|
|
|
|
{
|
|
|
|
|
logger.Info("No certificate filename specified.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (File.Exists(fileName))
|
|
|
|
|
{
|
|
|
|
|
logger.Info("Certificate file already exists. To regenerate, delete {0}", fileName);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string x500 = string.Format("CN={0}", hostname);
|
|
|
|
|
|
|
|
|
|
DateTime startTime = DateTime.Now.AddDays(-2);
|
|
|
|
|
DateTime endTime = DateTime.Now.AddYears(10);
|
|
|
|
|
|
|
|
|
|
byte[] pfxData = CreateSelfSignCertificatePfx(
|
|
|
|
|
x500,
|
|
|
|
|
startTime,
|
|
|
|
|
endTime);
|
|
|
|
|
|
|
|
|
|
File.WriteAllBytes(fileName, pfxData);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
logger.ErrorException("Error generating self signed ssl certificate: {0}", e, fileName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static byte[] CreateSelfSignCertificatePfx(
|
|
|
|
|
string x500,
|
|
|
|
|
DateTime startTime,
|
|
|
|
|
DateTime endTime)
|
|
|
|
|
{
|
|
|
|
|
byte[] pfxData;
|
|
|
|
|
|
|
|
|
|
if (x500 == null)
|
|
|
|
|
{
|
|
|
|
|
x500 = "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SystemTime startSystemTime = ToSystemTime(startTime);
|
|
|
|
|
SystemTime endSystemTime = ToSystemTime(endTime);
|
|
|
|
|
string containerName = Guid.NewGuid().ToString();
|
|
|
|
|
|
|
|
|
|
GCHandle dataHandle = new GCHandle();
|
|
|
|
|
IntPtr providerContext = IntPtr.Zero;
|
|
|
|
|
IntPtr cryptKey = IntPtr.Zero;
|
|
|
|
|
IntPtr certContext = IntPtr.Zero;
|
|
|
|
|
IntPtr certStore = IntPtr.Zero;
|
|
|
|
|
IntPtr storeCertContext = IntPtr.Zero;
|
|
|
|
|
IntPtr passwordPtr = IntPtr.Zero;
|
|
|
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Check(NativeMethods.CryptAcquireContextW(
|
|
|
|
|
out providerContext,
|
|
|
|
|
containerName,
|
|
|
|
|
null,
|
|
|
|
|
1, // PROV_RSA_FULL
|
|
|
|
|
8)); // CRYPT_NEWKEYSET
|
|
|
|
|
|
|
|
|
|
Check(NativeMethods.CryptGenKey(
|
|
|
|
|
providerContext,
|
|
|
|
|
1, // AT_KEYEXCHANGE
|
|
|
|
|
1 | 2048 << 16, // CRYPT_EXPORTABLE 2048 bit key
|
|
|
|
|
out cryptKey));
|
|
|
|
|
|
|
|
|
|
IntPtr errorStringPtr;
|
|
|
|
|
int nameDataLength = 0;
|
|
|
|
|
byte[] nameData;
|
|
|
|
|
|
|
|
|
|
// errorStringPtr gets a pointer into the middle of the x500 string,
|
|
|
|
|
// so x500 needs to be pinned until after we've copied the value
|
|
|
|
|
// of errorStringPtr.
|
|
|
|
|
dataHandle = GCHandle.Alloc(x500, GCHandleType.Pinned);
|
|
|
|
|
|
|
|
|
|
if (!NativeMethods.CertStrToNameW(
|
|
|
|
|
0x00010001, // X509_ASN_ENCODING | PKCS_7_ASN_ENCODING
|
|
|
|
|
dataHandle.AddrOfPinnedObject(),
|
|
|
|
|
3, // CERT_X500_NAME_STR = 3
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
null,
|
|
|
|
|
ref nameDataLength,
|
|
|
|
|
out errorStringPtr))
|
|
|
|
|
{
|
|
|
|
|
string error = Marshal.PtrToStringUni(errorStringPtr);
|
|
|
|
|
throw new ArgumentException(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nameData = new byte[nameDataLength];
|
|
|
|
|
|
|
|
|
|
if (!NativeMethods.CertStrToNameW(
|
|
|
|
|
0x00010001, // X509_ASN_ENCODING | PKCS_7_ASN_ENCODING
|
|
|
|
|
dataHandle.AddrOfPinnedObject(),
|
|
|
|
|
3, // CERT_X500_NAME_STR = 3
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
nameData,
|
|
|
|
|
ref nameDataLength,
|
|
|
|
|
out errorStringPtr))
|
|
|
|
|
{
|
|
|
|
|
string error = Marshal.PtrToStringUni(errorStringPtr);
|
|
|
|
|
throw new ArgumentException(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dataHandle.Free();
|
|
|
|
|
|
|
|
|
|
dataHandle = GCHandle.Alloc(nameData, GCHandleType.Pinned);
|
|
|
|
|
CryptoApiBlob nameBlob = new CryptoApiBlob(
|
|
|
|
|
nameData.Length,
|
|
|
|
|
dataHandle.AddrOfPinnedObject());
|
|
|
|
|
|
|
|
|
|
CryptKeyProviderInformation kpi = new CryptKeyProviderInformation();
|
|
|
|
|
kpi.ContainerName = containerName;
|
|
|
|
|
kpi.ProviderType = 1; // PROV_RSA_FULL
|
|
|
|
|
kpi.KeySpec = 1; // AT_KEYEXCHANGE
|
|
|
|
|
|
|
|
|
|
CryptAlgorithmIdentifier sha256Identifier = new CryptAlgorithmIdentifier();
|
|
|
|
|
sha256Identifier.pszObjId = "1.2.840.113549.1.1.11";
|
|
|
|
|
|
|
|
|
|
certContext = NativeMethods.CertCreateSelfSignCertificate(
|
|
|
|
|
providerContext,
|
|
|
|
|
ref nameBlob,
|
|
|
|
|
0,
|
|
|
|
|
ref kpi,
|
|
|
|
|
ref sha256Identifier,
|
|
|
|
|
ref startSystemTime,
|
|
|
|
|
ref endSystemTime,
|
|
|
|
|
IntPtr.Zero);
|
|
|
|
|
Check(certContext != IntPtr.Zero);
|
|
|
|
|
dataHandle.Free();
|
|
|
|
|
|
|
|
|
|
certStore = NativeMethods.CertOpenStore(
|
|
|
|
|
"Memory", // sz_CERT_STORE_PROV_MEMORY
|
|
|
|
|
0,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
0x2000, // CERT_STORE_CREATE_NEW_FLAG
|
|
|
|
|
IntPtr.Zero);
|
|
|
|
|
Check(certStore != IntPtr.Zero);
|
|
|
|
|
|
|
|
|
|
Check(NativeMethods.CertAddCertificateContextToStore(
|
|
|
|
|
certStore,
|
|
|
|
|
certContext,
|
|
|
|
|
1, // CERT_STORE_ADD_NEW
|
|
|
|
|
out storeCertContext));
|
|
|
|
|
|
|
|
|
|
NativeMethods.CertSetCertificateContextProperty(
|
|
|
|
|
storeCertContext,
|
|
|
|
|
2, // CERT_KEY_PROV_INFO_PROP_ID
|
|
|
|
|
0,
|
|
|
|
|
ref kpi);
|
|
|
|
|
|
|
|
|
|
CryptoApiBlob pfxBlob = new CryptoApiBlob();
|
|
|
|
|
Check(NativeMethods.PFXExportCertStoreEx(
|
|
|
|
|
certStore,
|
|
|
|
|
ref pfxBlob,
|
|
|
|
|
passwordPtr,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
7)); // EXPORT_PRIVATE_KEYS | REPORT_NO_PRIVATE_KEY | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY
|
|
|
|
|
|
|
|
|
|
pfxData = new byte[pfxBlob.DataLength];
|
|
|
|
|
dataHandle = GCHandle.Alloc(pfxData, GCHandleType.Pinned);
|
|
|
|
|
pfxBlob.Data = dataHandle.AddrOfPinnedObject();
|
|
|
|
|
Check(NativeMethods.PFXExportCertStoreEx(
|
|
|
|
|
certStore,
|
|
|
|
|
ref pfxBlob,
|
|
|
|
|
passwordPtr,
|
|
|
|
|
IntPtr.Zero,
|
|
|
|
|
7)); // EXPORT_PRIVATE_KEYS | REPORT_NO_PRIVATE_KEY | REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY
|
|
|
|
|
dataHandle.Free();
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
if (passwordPtr != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
Marshal.ZeroFreeCoTaskMemUnicode(passwordPtr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dataHandle.IsAllocated)
|
|
|
|
|
{
|
|
|
|
|
dataHandle.Free();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (certContext != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
NativeMethods.CertFreeCertificateContext(certContext);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (storeCertContext != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
NativeMethods.CertFreeCertificateContext(storeCertContext);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (certStore != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
NativeMethods.CertCloseStore(certStore, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cryptKey != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
NativeMethods.CryptDestroyKey(cryptKey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (providerContext != IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
NativeMethods.CryptReleaseContext(providerContext, 0);
|
|
|
|
|
NativeMethods.CryptAcquireContextW(
|
|
|
|
|
out providerContext,
|
|
|
|
|
containerName,
|
|
|
|
|
null,
|
|
|
|
|
1, // PROV_RSA_FULL
|
|
|
|
|
0x10); // CRYPT_DELETEKEYSET
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pfxData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static SystemTime ToSystemTime(DateTime dateTime)
|
|
|
|
|
{
|
|
|
|
|
long fileTime = dateTime.ToFileTime();
|
|
|
|
|
SystemTime systemTime;
|
|
|
|
|
Check(NativeMethods.FileTimeToSystemTime(ref fileTime, out systemTime));
|
|
|
|
|
return systemTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void Check(bool nativeCallSucceeded)
|
|
|
|
|
{
|
|
|
|
|
if (!nativeCallSucceeded)
|
|
|
|
|
{
|
|
|
|
|
int error = Marshal.GetHRForLastWin32Error();
|
|
|
|
|
Marshal.ThrowExceptionForHR(error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|