Skip to content

Commit

Permalink
Fixed a few SNI related issues for IIS 7 Express.
Browse files Browse the repository at this point in the history
  • Loading branch information
lextm committed Jul 30, 2017
1 parent 3e0c6f8 commit 1792e2c
Show file tree
Hide file tree
Showing 15 changed files with 207 additions and 134 deletions.
189 changes: 99 additions & 90 deletions JexusManager.Shared/BindingUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,20 @@ public static class BindingUtility

internal static void Reinitialize(this Binding original, Binding binding)
{
if (original.GetIsSni() && (!binding.GetIsSni() || original.Host != binding.Host))
if (original.Parent.Parent.Server.SupportsSni)
{
original.CleanUpSni();
if (original.GetIsSni() && (!binding.GetIsSni() || original.Host != binding.Host))
{
original.CleanUpSni();
}

original.SslFlags = binding.SslFlags;
}

original.BindingInformation = binding.BindingInformation;
original.Protocol = binding.Protocol;
original.CertificateHash = binding.CertificateHash;
original.CertificateStoreName = binding.CertificateStoreName;
original.SslFlags = binding.SslFlags;
binding.Delete();
}

Expand All @@ -45,114 +49,119 @@ internal static string FixCertificateMapping(this Binding binding, X509Certifica
return string.Empty;
}

if (binding.GetIsSni())
if (binding.Parent.Parent.Server.SupportsSni)
{
if (certificate2.GetNameInfo(X509NameType.DnsName, false) != binding.Host)
if (binding.GetIsSni())
{
return "SNI mode requires host name matches common name of the certificate";
}
if (certificate2.GetNameInfo(X509NameType.DnsName, false) != binding.Host)
{
return "SNI mode requires host name matches common name of the certificate";
}

// handle SNI
var sni = NativeMethods.QuerySslSniInfo(new Tuple<string, int>(binding.Host, binding.EndPoint.Port));
if (sni == null)
{
try
// handle SNI
var sni = NativeMethods.QuerySslSniInfo(new Tuple<string, int>(binding.Host,
binding.EndPoint.Port));
if (sni == null)
{
// register mapping
using (var process = new Process())
try
{
var start = process.StartInfo;
start.Verb = "runas";
start.FileName = "cmd";
start.Arguments = string.Format(
"/c \"\"{2}\" /h:\"{0}\" /s:{1}\" /i:{3} /a:{4} /o:{5} /x:{6}",
Hex.ToHexString(binding.CertificateHash),
binding.CertificateStoreName,
Path.Combine(Environment.CurrentDirectory, "certificateinstaller.exe"),
AppIdIisExpress,
binding.EndPoint.Address,
binding.EndPoint.Port,
binding.Host);
start.CreateNoWindow = true;
start.WindowStyle = ProcessWindowStyle.Hidden;
process.Start();
process.WaitForExit();

if (process.ExitCode != 0)
// register mapping
using (var process = new Process())
{
return "Register new certificate failed: access is denied";
var start = process.StartInfo;
start.Verb = "runas";
start.FileName = "cmd";
start.Arguments = string.Format(
"/c \"\"{2}\" /h:\"{0}\" /s:{1}\" /i:{3} /a:{4} /o:{5} /x:{6}",
Hex.ToHexString(binding.CertificateHash),
binding.CertificateStoreName,
Path.Combine(Environment.CurrentDirectory, "certificateinstaller.exe"),
AppIdIisExpress,
binding.EndPoint.Address,
binding.EndPoint.Port,
binding.Host);
start.CreateNoWindow = true;
start.WindowStyle = ProcessWindowStyle.Hidden;
process.Start();
process.WaitForExit();

if (process.ExitCode != 0)
{
return "Register new certificate failed: access is denied";
}

return string.Empty;
}

return string.Empty;
}
catch (Exception)
{
// elevation is cancelled.
return "Register new certificate failed: operation is cancelled";
}
}
catch (Exception)
{
// elevation is cancelled.
return "Register new certificate failed: operation is cancelled";
}
}

if (!sni.Hash.SequenceEqual(binding.CertificateHash))
{
// TODO: fix the error message.
var result =
MessageBox.Show(
"At least one other site is using the same HTTPS binding and the binding is configured with a different certificate. Are you sure that you want to reuse this HTTPS binding and reassign the other site or sites to use the new certificate?",
"TODO",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1);
if (result != DialogResult.Yes)
if (!sni.Hash.SequenceEqual(binding.CertificateHash))
{
return "Certificate hash does not match. Please use the certificate that matches HTTPS binding";
}
// TODO: fix the error message.
var result =
MessageBox.Show(
"At least one other site is using the same HTTPS binding and the binding is configured with a different certificate. Are you sure that you want to reuse this HTTPS binding and reassign the other site or sites to use the new certificate?",
"TODO",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1);
if (result != DialogResult.Yes)
{
return
"Certificate hash does not match. Please use the certificate that matches HTTPS binding";
}

try
{
// register mapping
using (var process = new Process())
try
{
var start = process.StartInfo;
start.Verb = "runas";
start.FileName = "cmd";
start.Arguments = string.Format(
"/c \"\"{2}\" /h:\"{0}\" /s:{1}\" /i:{3} /a:{4} /o:{5} /x:{6}",
Hex.ToHexString(binding.CertificateHash),
binding.CertificateStoreName,
Path.Combine(Environment.CurrentDirectory, "certificateinstaller.exe"),
AppIdIisExpress,
binding.EndPoint.Address,
binding.EndPoint.Port,
binding.Host);
start.CreateNoWindow = true;
start.WindowStyle = ProcessWindowStyle.Hidden;
process.Start();
process.WaitForExit();

if (process.ExitCode != 0)
// register mapping
using (var process = new Process())
{
return "Register new certificate failed: access is denied";
var start = process.StartInfo;
start.Verb = "runas";
start.FileName = "cmd";
start.Arguments = string.Format(
"/c \"\"{2}\" /h:\"{0}\" /s:{1}\" /i:{3} /a:{4} /o:{5} /x:{6}",
Hex.ToHexString(binding.CertificateHash),
binding.CertificateStoreName,
Path.Combine(Environment.CurrentDirectory, "certificateinstaller.exe"),
AppIdIisExpress,
binding.EndPoint.Address,
binding.EndPoint.Port,
binding.Host);
start.CreateNoWindow = true;
start.WindowStyle = ProcessWindowStyle.Hidden;
process.Start();
process.WaitForExit();

if (process.ExitCode != 0)
{
return "Register new certificate failed: access is denied";
}

return string.Empty;
}

return string.Empty;
}
catch (Exception)
{
// elevation is cancelled.
return "Register new certificate failed: operation is cancelled";
}
}
catch (Exception)

if (!string.Equals(sni.StoreName, binding.CertificateStoreName, StringComparison.OrdinalIgnoreCase))
{
// elevation is cancelled.
return "Register new certificate failed: operation is cancelled";
// TODO: can this happen?
return
"Certificate store name does not match. Please use the certificate that matches HTTPS binding";
}
}

if (!string.Equals(sni.StoreName, binding.CertificateStoreName, StringComparison.OrdinalIgnoreCase))
{
// TODO: can this happen?
return
"Certificate store name does not match. Please use the certificate that matches HTTPS binding";
return string.Empty;
}

return string.Empty;
}

// handle IP based
Expand Down
7 changes: 6 additions & 1 deletion JexusManager/Dialogs/NewSiteDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,17 @@ public NewSiteDialog(IServiceProvider serviceProvider, SiteCollection collection
InitializeComponent();
cbType.SelectedIndex = 0;
_collection = collection;
if (collection.Parent == null)
{
throw new InvalidOperationException("null server for site collection");
}

btnBrowse.Visible = collection.Parent.IsLocalhost;
txtPool.Text = collection.Parent.ApplicationDefaults.ApplicationPoolName;
btnChoose.Enabled = collection.Parent.Mode != WorkingMode.Jexus;
txtHost.Text = collection.Parent.Mode == WorkingMode.IisExpress ? "localhost" : string.Empty;
DialogHelper.LoadAddresses(cbAddress);
if (Environment.OSVersion.Version < new Version(6, 2))
if (!collection.Parent.SupportsSni)
{
cbSniRequired.Enabled = false;
}
Expand Down
49 changes: 23 additions & 26 deletions JexusManager/Features/Main/BindingDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,38 @@ namespace JexusManager.Features.Main

public sealed partial class BindingDialog : DialogForm
{
private Binding _binding;
private readonly Site _site;

public BindingDialog(IServiceProvider serviceProvider, Binding binding, Site site)
: base(serviceProvider)
{
InitializeComponent();
_binding = binding;
Binding = binding;
_site = site;
Text = _binding == null ? "Create Site Binding" : "Edit Site Binding";
Text = Binding == null ? "Create Site Binding" : "Edit Site Binding";
DialogHelper.LoadAddresses(cbAddress);
txtPort.Text = "80";
cbType.SelectedIndex = 0;
if (Environment.OSVersion.Version < new Version(6, 2))
if (!Binding.Parent.Parent.Server.SupportsSni)
{
cbSniRequired.Enabled = false;
}

if (_binding == null)
if (Binding == null)
{
txtHost.Text = site.Server.Mode == WorkingMode.IisExpress ? "localhost" : string.Empty;
return;
}

cbType.Text = _binding.Protocol;
cbType.Enabled = _binding == null;
cbAddress.Text = _binding.EndPoint.Address.AddressToCombo();
txtPort.Text = _binding.EndPoint.Port.ToString();
txtHost.Text = _binding.Host.HostToDisplay();
cbSniRequired.Checked = _binding.GetIsSni();
cbType.Text = Binding.Protocol;
cbType.Enabled = Binding == null;
cbAddress.Text = Binding.EndPoint.Address.AddressToCombo();
txtPort.Text = Binding.EndPoint.Port.ToString();
txtHost.Text = Binding.Host.HostToDisplay();
if (Binding.Parent.Parent.Server.SupportsSni)
{
cbSniRequired.Checked = Binding.GetIsSni();
}
}

private void CbTypeSelectedIndexChanged(object sender, EventArgs e)
Expand Down Expand Up @@ -118,18 +120,16 @@ private void BtnOkClick(object sender, EventArgs e)
var host = txtHost.Text.DisplayToHost();
var binding = new Binding(
cbType.Text,
string.Format("{0}:{1}:{2}", address.AddressToDisplay(), port, host.HostToDisplay()),
$"{address.AddressToDisplay()}:{port}:{host.HostToDisplay()}",
cbType.Text == "https" ? certificate?.Certificate.GetCertHash() : new byte[0],
cbType.Text == "https" ? certificate?.Store : null,
cbSniRequired.Checked ? SslFlags.Sni : SslFlags.None,
_site.Bindings);
var matched = _site.Parent.FindDuplicate(binding, _site, _binding);
var matched = _site.Parent.FindDuplicate(binding, _site, Binding);
if (matched == true)
{
var result = ShowMessage(
string.Format(
"The binding '{0}' is assigned to another site. If you assign the same binding to this site, you will only be able to start one of the sites. Are you sure that you want to add this duplicate binding?",
_binding),
$"The binding '{Binding}' is assigned to another site. If you assign the same binding to this site, you will only be able to start one of the sites. Are you sure that you want to add this duplicate binding?",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1);
Expand All @@ -149,32 +149,29 @@ private void BtnOkClick(object sender, EventArgs e)
return;
}

if (_binding == null)
if (Binding == null)
{
_binding = binding;
Binding = binding;
}
else
{
_binding.Reinitialize(binding);
Binding.Reinitialize(binding);
}

if (_site.Server.Mode == WorkingMode.IisExpress)
{
var result = _binding.FixCertificateMapping(certificate?.Certificate);
var result = Binding.FixCertificateMapping(certificate?.Certificate);
if (!string.IsNullOrEmpty(result))
{
MessageBox.Show(string.Format("The binding '{0}' is invalid: {1}", _binding, result), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show($"The binding '{Binding}' is invalid: {result}", Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}

DialogResult = DialogResult.OK;
}

internal Binding Binding
{
get { return _binding; }
}
internal Binding Binding { get; private set; }

private void CbAddressTextChanged(object sender, EventArgs e)
{
Expand Down Expand Up @@ -203,7 +200,7 @@ private void BindingDialogHelpButtonClicked(object sender, CancelEventArgs e)
private void BindingDialogLoad(object sender, EventArgs e)
{
var service = (IConfigurationService)GetService(typeof(IConfigurationService));
DialogHelper.LoadCertificates(cbCertificates, _binding?.CertificateHash, service);
DialogHelper.LoadCertificates(cbCertificates, Binding?.CertificateHash, service);
}

private void CbCertificatesSelectedIndexChanged(object sender, EventArgs e)
Expand Down
4 changes: 3 additions & 1 deletion JexusManager/Features/Main/BindingsDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ private void ListView1SelectedIndexChanged(object sender, EventArgs e)
return;
}

var toElevate = selected && ((Binding)listView1.SelectedItems[0].Tag).GetIsSni();
var binding = (Binding)listView1.SelectedItems[0].Tag;
var supportsSni = binding.Parent.Parent.Server.SupportsSni;
var toElevate = selected && (!supportsSni || (supportsSni && binding.GetIsSni()));
if (toElevate)
{
JexusManager.NativeMethods.TryAddShieldToButton(btnRemove);
Expand Down
6 changes: 5 additions & 1 deletion JexusManager/Features/Main/SslDiagDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ private void BtnGenerateClick(object sender, System.EventArgs e)
{
var hashString = Hex.ToHexString(binding.CertificateHash);
Debug($"SSLCertHash: {hashString}");
Debug($"SSL Flags: {binding.SslFlags}");
if (site.Server.SupportsSni)
{
Debug($"SSL Flags: {binding.SslFlags}");
}

Debug("Testing EndPoint: 127.0.0.1");

var personal = new X509Store(binding.CertificateStoreName, StoreLocation.LocalMachine);
Expand Down
Loading

0 comments on commit 1792e2c

Please sign in to comment.