Merge pull request #986 from jabbera/AutoCertGeneration

Create self signed cert if one does not exist
pull/702/head
Luke 10 years ago
commit df56b1ec8a

@ -2,6 +2,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using System.Collections.Generic;
using System.Net;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Common.Net
{
@ -51,5 +52,12 @@ namespace MediaBrowser.Common.Net
/// <param name="endpoint">The endpoint.</param>
/// <returns><c>true</c> if [is in local network] [the specified endpoint]; otherwise, <c>false</c>.</returns>
bool IsInLocalNetwork(string endpoint);
/// <summary>
/// Generates a self signed certificate at the locatation specified by <paramref name="certificatePath"/>.
/// </summary>
/// <param name="certificatePath">The path to generate the certificate.</param>
/// <param name="hostname">The common name for the certificate.</param>
void GenerateSelfSignedSslCertificate(string certificatePath, string hostname);
}
}

@ -57,6 +57,11 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll</HintPath>
</Reference>
<Reference Include="Mono.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\ThirdParty\Mono.Security\Mono.Security.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="ServiceStack.Interfaces">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Interfaces.dll</HintPath>
@ -70,6 +75,7 @@
<Link>Properties\SharedVersion.cs</Link>
</Compile>
<Compile Include="Native\BaseMonoApp.cs" />
<Compile Include="Networking\CertificateGenerator.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Native\NativeApp.cs" />

@ -0,0 +1,88 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Model.Logging;
using Mono.Security.X509;
namespace MediaBrowser.Server.Mono.Networking
{
internal class CertificateGenerator
{
private const string MonoTestRootAgency = "<RSAKeyValue><Modulus>v/4nALBxCE+9JgEC0LnDUvKh6e96PwTpN4Rj+vWnqKT7IAp1iK/JjuqvAg6DQ2vTfv0dTlqffmHH51OyioprcT5nzxcSTsZb/9jcHScG0s3/FRIWnXeLk/fgm7mSYhjUaHNI0m1/NTTktipicjKxo71hGIg9qucCWnDum+Krh/k=</Modulus><Exponent>AQAB</Exponent><P>9jbKxMXEruW2CfZrzhxtull4O8P47+mNsEL+9gf9QsRO1jJ77C+jmzfU6zbzjf8+ViK+q62tCMdC1ZzulwdpXQ==</P><Q>x5+p198l1PkK0Ga2mRh0SIYSykENpY2aLXoyZD/iUpKYAvATm0/wvKNrE4dKJyPCA+y3hfTdgVag+SP9avvDTQ==</Q><DP>ISSjCvXsUfbOGG05eddN1gXxL2pj+jegQRfjpk7RAsnWKvNExzhqd5x+ZuNQyc6QH5wxun54inP4RTUI0P/IaQ==</DP><DQ>R815VQmR3RIbPqzDXzv5j6CSH6fYlcTiQRtkBsUnzhWmkd/y3XmamO+a8zJFjOCCx9CcjpVuGziivBqi65lVPQ==</DQ><InverseQ>iYiu0KwMWI/dyqN3RJYUzuuLj02/oTD1pYpwo2rvNCXU1Q5VscOeu2DpNg1gWqI+1RrRCsEoaTNzXB1xtKNlSw==</InverseQ><D>nIfh1LYF8fjRBgMdAH/zt9UKHWiaCnc+jXzq5tkR8HVSKTVdzitD8bl1JgAfFQD8VjSXiCJqluexy/B5SGrCXQ49c78NIQj0hD+J13Y8/E0fUbW1QYbhj6Ff7oHyhaYe1WOQfkp2t/h+llHOdt1HRf7bt7dUknYp7m8bQKGxoYE=</D></RSAKeyValue>";
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;
}
byte[] sn = Guid.NewGuid().ToByteArray();
string subject = string.Format("CN={0}", hostname);
string issuer = subject;
DateTime notBefore = DateTime.Now.AddDays(-2);
DateTime notAfter = DateTime.Now.AddYears(10);
RSA issuerKey = RSA.Create();
issuerKey.FromXmlString(MonoTestRootAgency);
RSA subjectKey = RSA.Create();
// serial number MUST be positive
if ((sn[0] & 0x80) == 0x80)
sn[0] -= 0x80;
issuer = subject;
issuerKey = subjectKey;
X509CertificateBuilder cb = new X509CertificateBuilder(3);
cb.SerialNumber = sn;
cb.IssuerName = issuer;
cb.NotBefore = notBefore;
cb.NotAfter = notAfter;
cb.SubjectName = subject;
cb.SubjectPublicKey = subjectKey;
// signature
cb.Hash = "SHA256";
byte[] rawcert = cb.Sign(issuerKey);
PKCS12 p12 = new PKCS12();
ArrayList list = new ArrayList();
// we use a fixed array to avoid endianess issues
// (in case some tools requires the ID to be 1).
list.Add(new byte[4] {1, 0, 0, 0});
Hashtable attributes = new Hashtable(1);
attributes.Add(PKCS9.localKeyId, list);
p12.AddCertificate(new X509Certificate(rawcert), attributes);
p12.AddPkcs8ShroudedKeyBag(subjectKey, attributes);
p12.SaveToFile(fileName);
}
catch (Exception e)
{
logger.ErrorException("Error generating self signed ssl certificate: {0}", e, fileName);
}
}
}
}

@ -35,5 +35,15 @@ namespace MediaBrowser.Server.Mono.Networking
{
return new List<FileSystemEntryInfo> ();
}
/// <summary>
/// Generates a self signed certificate at the locatation specified by <paramref name="certificatePath"/>.
/// </summary>
/// <param name="certificatePath">The path to generate the certificate.</param>
/// <param name="hostname">The common name for the certificate.</param>
public void GenerateSelfSignedSslCertificate(string certificatePath, string hostname)
{
CertificateGenerator.CreateSelfSignCertificatePfx(certificatePath, hostname, Logger);
}
}
}

@ -779,6 +779,13 @@ namespace MediaBrowser.Server.Startup.Common
{
try
{
if (ServerConfigurationManager.Configuration.EnableHttps)
{
NetworkManager.GenerateSelfSignedSslCertificate(
ServerConfigurationManager.Configuration.CertificatePath,
GetHostnameFromExternalDns(ServerConfigurationManager.Configuration.WanDdns));
}
ServerManager.Start(GetUrlPrefixes(), ServerConfigurationManager.Configuration.CertificatePath);
}
catch (Exception ex)
@ -1183,5 +1190,29 @@ namespace MediaBrowser.Server.Startup.Common
NativeApp.ConfigureAutoRun(autorun);
}
}
/// <summary>
/// This returns localhost in the case of no external dns, and the hostname if the
/// dns is prefixed with a valid Uri prefix.
/// </summary>
/// <param name="externalDns">The external dns prefix to get the hostname of.</param>
/// <returns>The hostname in <paramref name="externalDns"/></returns>
private static string GetHostnameFromExternalDns(string externalDns)
{
if (string.IsNullOrWhiteSpace(externalDns))
{
return "localhost";
}
try
{
Uri uri = new Uri(externalDns);
return uri.Host;
}
catch (Exception e)
{
return externalDns;
}
}
}
}

@ -109,6 +109,7 @@
<Compile Include="Native\Standby.cs" />
<Compile Include="Native\ServerAuthorization.cs" />
<Compile Include="Native\WindowsApp.cs" />
<Compile Include="Networking\CertificateGenerator.cs" />
<Compile Include="Networking\NativeMethods.cs" />
<Compile Include="Networking\NetworkManager.cs" />
<Compile Include="Networking\NetworkShares.cs" />

@ -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);
}
}
}
}

@ -47,9 +47,108 @@ namespace MediaBrowser.ServerApplication.Networking
/// <returns>System.Int32.</returns>
[DllImport("Netapi32", SetLastError = true),
SuppressUnmanagedCodeSecurity]
public static extern int NetApiBufferFree(
IntPtr pBuf);
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FileTimeToSystemTime(
[In] ref long fileTime,
out SystemTime systemTime);
[DllImport("AdvApi32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptAcquireContextW(
out IntPtr providerContext,
[MarshalAs(UnmanagedType.LPWStr)] string container,
[MarshalAs(UnmanagedType.LPWStr)] string provider,
int providerType,
int flags);
[DllImport("AdvApi32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptReleaseContext(
IntPtr providerContext,
int flags);
[DllImport("AdvApi32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptGenKey(
IntPtr providerContext,
int algorithmId,
int flags,
out IntPtr cryptKeyHandle);
[DllImport("AdvApi32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptDestroyKey(
IntPtr cryptKeyHandle);
[DllImport("Crypt32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CertStrToNameW(
int certificateEncodingType,
IntPtr x500,
int strType,
IntPtr reserved,
[MarshalAs(UnmanagedType.LPArray)] [Out] byte[] encoded,
ref int encodedLength,
out IntPtr errorString);
[DllImport("Crypt32.dll", SetLastError = true, ExactSpelling = true)]
public static extern IntPtr CertCreateSelfSignCertificate(
IntPtr providerHandle,
[In] ref CryptoApiBlob subjectIssuerBlob,
int flags,
[In] ref CryptKeyProviderInformation keyProviderInformation,
[In] ref CryptAlgorithmIdentifier algorithmIdentifier,
[In] ref SystemTime startTime,
[In] ref SystemTime endTime,
IntPtr extensions);
[DllImport("Crypt32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CertFreeCertificateContext(
IntPtr certificateContext);
[DllImport("Crypt32.dll", SetLastError = true, ExactSpelling = true)]
public static extern IntPtr CertOpenStore(
[MarshalAs(UnmanagedType.LPStr)] string storeProvider,
int messageAndCertificateEncodingType,
IntPtr cryptProvHandle,
int flags,
IntPtr parameters);
[DllImport("Crypt32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CertCloseStore(
IntPtr certificateStoreHandle,
int flags);
[DllImport("Crypt32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CertAddCertificateContextToStore(
IntPtr certificateStoreHandle,
IntPtr certificateContext,
int addDisposition,
out IntPtr storeContextPtr);
[DllImport("Crypt32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CertSetCertificateContextProperty(
IntPtr certificateContext,
int propertyId,
int flags,
[In] ref CryptKeyProviderInformation data);
[DllImport("Crypt32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool PFXExportCertStoreEx(
IntPtr certificateStoreHandle,
ref CryptoApiBlob pfxBlob,
IntPtr password,
IntPtr reserved,
int flags);
}
//create a _SERVER_INFO_100 STRUCTURE
@ -69,4 +168,59 @@ namespace MediaBrowser.ServerApplication.Networking
[MarshalAs(UnmanagedType.LPWStr)]
internal string sv100_name;
}
[StructLayout(LayoutKind.Sequential)]
public struct SystemTime
{
public short Year;
public short Month;
public short DayOfWeek;
public short Day;
public short Hour;
public short Minute;
public short Second;
public short Milliseconds;
}
[StructLayout(LayoutKind.Sequential)]
public struct CryptObjIdBlob
{
public uint cbData;
public IntPtr pbData;
}
[StructLayout(LayoutKind.Sequential)]
public struct CryptAlgorithmIdentifier
{
[MarshalAs(UnmanagedType.LPStr)]
public String pszObjId;
public CryptObjIdBlob Parameters;
}
[StructLayout(LayoutKind.Sequential)]
public struct CryptoApiBlob
{
public int DataLength;
public IntPtr Data;
public CryptoApiBlob(int dataLength, IntPtr data)
{
this.DataLength = dataLength;
this.Data = data;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct CryptKeyProviderInformation
{
[MarshalAs(UnmanagedType.LPWStr)]
public string ContainerName;
[MarshalAs(UnmanagedType.LPWStr)]
public string ProviderName;
public int ProviderType;
public int Flags;
public int ProviderParameterCount;
public IntPtr ProviderParameters; // PCRYPT_KEY_PROV_PARAM
public int KeySpec;
}
}

@ -155,6 +155,16 @@ namespace MediaBrowser.ServerApplication.Networking
});
}
/// <summary>
/// Generates a self signed certificate at the locatation specified by <paramref name="certificatePath"/>.
/// </summary>
/// <param name="certificatePath">The path to generate the certificate.</param>
/// <param name="hostname">The common name for the certificate.</param>
public void GenerateSelfSignedSslCertificate(string certificatePath, string hostname)
{
CertificateGenerator.CreateSelfSignCertificatePfx(certificatePath, hostname, Logger);
}
/// <summary>
/// Gets the network prefix.
/// </summary>

Loading…
Cancel
Save