Adding SSL Wildcard Certificates to IIS Webs

March 21, 2017

As web browsers start to issue warnings on plain http websites if you are asked to input username/password, it’s time to add SSL certificates even on dev/test servers. We can expect more aggressive warnings in the future ūüėČ

Apparently there is a way to create a self-signed certificate built into IIS (screenshot from Windows Server 2008)

iis create certificate

but this seems to create cerficates only for the host name, not for any domain hosted on the machine.

Back to square one, start up a current Linux machine, and make sure your openssl is newer than version 1.0.1f. (Remember Heartbeed?).

The instructions I found to create self-signed certificates are nearly identical (source, source, source)

openssl genrsa 2048 > my-host.key
openssl req -new -x509 -nodes -sha1 -days 3650 -key my-host.key > my-host.cert
# make sure Common Name starts with "*.", e.g. *.my-host.com
openssl x509 -noout -fingerprint -text < my-host.cert > my-host.info
cat my-host.cert my-host.key > my-host.pem

For use in IIS, you need to create a .pfx from these certificate files:

openssl pkcs12 -inkey my-host.pem -in my-host.cert -export -out my-host.pfx

Copy the .pfx to your IIS machine.

In IIS Manager, select “Server Certificates” on the server node, click “Import…” to import the .pfx certificate.

Start up mmc, “File”, “Add/Remove Snap-in”, select “Certificates”, “Add”, “Computer account”, “Finish”, “OK”, (this click orgy shows you how important certificates were in 2008, as compared to Start/Administrative Tools/Data Sources (ODBC) ūüėČ ) and find the imported certificate(s) under

Console Root\Certificates\Personal\Certificates

Right-click each of them, select Properties, and make sure that the Friendly Name starts with “*.” for wild-card certificates. Otherwise, you cannot assign a host name for https web sites.

Back in IIS Manager, select each site you want to add https support, click Bindings, Add, select Type: https and select the wild-card SSL certificate. Only if the friendly name starts with *, you can/must set the site’s Host name. Click OK and you are done.

If you want your sites to redirect http to https automatically, make sure the Require SSL box is not checked in the site’s SSL Settings.

The minimal web.config to perform these redirects looks like this (source, source)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Redirect-HTTP-HTTPS-IIS">
          <match url="(.*)" />
          <conditions>
            <add input="{HTTPS}" pattern="^OFF$" ignoreCase="true" />
          </conditions>
          <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" 
            redirectType="Permanent" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Be aware that while these steps enable https for your IIS sites, self-signed certificates still require the users to explicitly accept the certificates in their browsers, which will raise an “Unknown issuer” warning at their first visit.

Update: There also seems to be a Powershell way to do it ūüėČ


Installing AWStats on Windows Server 2012

March 7, 2017

To install AWStats on Windows, first download the current version from awstats.org. If you don’t have Perl on your machine, get Strawberry Perl for Windows, as ActivePerl requires an annual Business License for production use.

On the server, create a web directory and a data directory for awstats. Follow the steps in the AWStats Setup Guide.

To access the log files of a remote IIS, I created a read-only share on c:\inetpub\LogFiles, and had to run

icacls c:\inetpub\LogFiles /reset /t

to allow non-admin access to the IIS log files.

To get Strawberry Perl to run on IIS, follow this Installation Guide:

  • In the Web Server role, you need to have the CGI feature installed.
  • In IIS Administrator, create a web site or application hosting AWStats. In the site or application, you need to add a Script Map for *.pl executing
C:\path\to\perl.exe "%s" %s

Things should be running by now if you browse to

http://myHost/awstats/cgi-bin/awstats.pl?config=mySite

I noticed that the stats only included data from the installation date (IIS logs are configured to daily log files).

Answers on the internetz suggest to merge old log files using logresolvemerg.pl, a script that ships with awstats.

C:\awstats\tools>perl logresolvemerge.pl [all my log files] > merged.log

Replace the LogFile entry in your config file(s) to point to the merged log file

LogFile="C:\awstats-data\merged.log"
#LogFile="\\path\to\LogFiles\W3SVC1\u_ex%YY-1%MM-1%DD-1.log"

and run

perl awstats.pl -config=mySite

again after deleting the previously generated data files.

Unfortunately, the merged log only resulted in “dropped” and “corrupted” records:

Phase 1 : First bypass old records, searching new record...
Searching new records from beginning of log file...
Jumped lines in file: 0
Parsed lines in file: 30376
Found 16100 dropped records,
Found 0 comments,
Found 0 blank records,
Found 14276 corrupted records,
Found 0 old records,
Found 0 new qualified records.

This may be caused by a number of reasons, but it turned out that the merged log requires a specific LogFormat:

LogFormat="%time2 %other %method %url %query %other %logname %host %ua %code %other %other %other"

Finally, I created a batch file awstats.cmd to update all my statistics

net use z: \\host\LogFiles awstats /user:awstats
d:
cd D:\wwwroot\awstats\wwwroot\cgi-bin
perl awstats.pl -update -config=mySite1
perl awstats.pl -update -config=mySite2
...
net use z: /delete

and created a scheduled task to automatically execute the script every day.


Handling the “Size Limit” in AD Queries

February 10, 2017

I created a small tool to mirror AD data into an SQL Server database. The AD queries essentially looked like this

var conn = new ADODB.Connection();
conn.Open("Provider=ADsDSOObject", "", "", 0);

const string where = "objectCategory='group' ";      

var qry = string.Format(@"SELECT objectCategory, displayName, [more attributes]
FROM 'LDAP://{0}/{1}' 
WHERE {2}", server, start, where);

object recs;
var rs = conn.Execute(qry, out recs, 0);

for (; !rs.EOF; rs.MoveNext())
{
    // ... process record
}

The attributes available in AD have been taken from here.

The code worked fine for many months, until one day it threw an exception:

System.Runtime.InteropServices.COMException (0x80072023): The size limit for this request was exceeded.
at ADODB.RecordsetClass.MoveNext()

It turned out that the query result suddenly exceeded 3000 records, which may or may not be a magic or configurable limit of fetches for a single AD query Рprobably also including the number of records each fetch returns. Who knows.

Thanks goes to the internetz, which provided me with a solution which now fetches more than 3000 records. Just replace the conn.Execute() part with

var cmd = new ADODB.Command {ActiveConnection = conn, CommandText = qry};
cmd.Properties["Page Size"].Value = 10000;
cmd.Properties["Timeout"].Value = 30;
cmd.Properties["Cache Results"].Value = false;

Problems using anti-forgery token

January 27, 2017

ASP.Net MVC provides an anti-forgery mechanism using the methods @Html.AntiForgeryToken() and the [ValidateAntiForgeryToken] attribute.

I developed a web application using this mechanism for login, as the MVC template automatically provides this code. It worked fine using Chrome, but when I tried out the application in Internet Explorer, a couple of errors occurred relating to it:

Anti-forgery token is meant for user “” but the current user is “username”

The provided anti-forgery token was meant for a different claims-based user

The different answers on SO and various blogs offered no solution:

  • setting¬†AntiForgeryConfig.SuppressIdentityHeuristicChecks to true
  • exchanging¬†the anti-forgery token
  • changing¬†AntiForgeryConfig.UniqueClaimTypeIdentifier

The solution that finally worked destroys the current ASP.Net session and signs out if the user is currently logged in:

[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
  Session.Abandon();
  if (AuthenticationManager.User.Identity.IsAuthenticated)
    AuthenticationManager.SignOut();
  ViewBag.ReturnUrl = returnUrl;
  return View();
}

Viewing ELMAH Logs of multiple applications

January 26, 2017

I recently added ELMAH and ELMAH.Mvc to a couple of web applications, and configured them to log into the same MSSQL database.

The table ELMAH_Error distinguishes the source of error in the columns Host (storing the host name of the web application) and Application (storing the IIS Configuration Path of the web application, for explanations see e.g. here).

By default, the /elmah handler only displays the error messages of the current application.

However, I wanted to see the errors of all ELMAH-enabled applications.

After some research into the C# code and the MSSQL database, the solution is surprisingly simply:

  • Create a new database, e.g. “elmah-read”
  • Create synonyms to the original table and the logging SP (we won’t really need this SP)
CREATE SYNONYM ELMAH_Error FOR Elmah.dbo.ELMAH_Error 
GO
CREATE SYNONYM ELMAH_LogError FOR Elmah.dbo.ELMAH_LogError 
GO
  • Copy the stored procedure¬†[dbo].[ELMAH_GetErrorXml] and remove the @Application¬†from the WHERE clause
WHERE [ErrorId] = @ErrorId
 --AND [Application] = @Application
  • Copy the stored procedure¬†[dbo].[ELMAH_GetErrorsXml], removing the @Application from the WHERE clauses, and extending the selected [host] column
 SELECT 
  errorId = [ErrorId], 
  application = [Application],
  host = [Host] + ' ' + 
    RIGHT( [Application], CHARINDEX( '/', REVERSE( [Application] ) + '/' ) - 1 ),
  • Create an empty ASP.Net Web Application in VS, add Elmah Core Library and¬†Elmah.Mvc from NuGet, and configure the connection string as in the original web applications
  • To allow remote access to the log viewer, add in web.config:
<elmah>
      <security allowRemoteAccess="true"/>
</elmah>
  • Don’t forget to¬†properly configure the Authentication feature of the log viewer application in IIS
  • Run

Fixing “The media could not be played.”

January 17, 2017

Firefox would not play embedded videos on Twitter. At first it displays the video’s preview image, but as soon as the video is loaded, it replaces the preview image with a black box containing the simple message

The media could not be played.

Now my browser has the FlashBlock add-on installed, and it could be the culprit.

So I checked the network traffic, and found the following domains to be involved:

  • twitter.com
  • abs.twimg.com for static content, such as gif, css, js
  • pbs.twimg.com for profile thumbnails
  • video.twimg.com for mp4’s

Adding video.twimg.com to FlashBlock’s whitelist did not change the behavior.

Whichever whitelisting semantics is built into FlashBlock, adding twitter.com solved my problem, and embedded videos now also play on Twitter.

 

 


Fixing PDFSharp hangs

January 12, 2017

To analyse a couple of PDF files whether they contain only images, I used the latest release build of PDFsharp, version 1.32.

However, when processing a certain file (of unknown origin) using code found in an SO answer

public static IEnumerable ExtractText(this PdfPage page)
{       
    var content = ContentReader.ReadContent(page);      
    var text = content.ExtractText();
    return text;
}   

the ExtractText() function simply would not return.

I upgraded to the most current build 1.50 beta 3, included the source in my project, and ran it in Debug mode, where execution halted in the file PDFsharp\src\PdfSharp\Pdf.Content\CParser.cs line 163 failing an assertion:

#if DEBUG
    default:
        Debug.Assert(false);
        break;
#endif

Without digging too deep into the analysis of PDF files, it was clear that the PDF contained a CSymbol that is not being handled by the library, and thus (most likely) ended up in an infinite loop inside CParser.ParseObject().

I fixed this by replacing the Debug.Assert statement with

        throw new Exception("unhandled PDF symbol " + symbol);

which fixed the situation for me.