Keith H’s boss came by his cube.

“Hey, you know how our insurance quote site has TLS enabled by default?”

“Yes,” Keith said. The insurance quote site was a notoriously kludgy .NET 4.5.1 web app, with no sort of automated deployment and two parallel development streams: one tracked in Git, and one done by editing files and compiling right on the production server.

“Yes, well, we need to turn that off. ‘Compliance reasons’.”

This created a number of problems for Keith. There was no way to know for sure what code was in Git and what was in production and how they didn’t match. Worse, they relied on reCAPTCHA, which required TLS. So Keith couldn’t just turn it off globally, he needed to disable it for inbound client connections but enable it for outbound connections.

Which he did. And everything was fine, until someone used the “Save as PDF” function, which took the page on the screen and saved it as a PDF to the user’s machine.

protected void btnInvoke_Click(object sender, EventArgs e)
        {
            var url = util.getUrl("Quote", "QuoteLetterPDF.aspx");
            url = url + "?QuoteId=" + hdnCurrentQuoteId.Value;
            var pdfBytes = UtilityManager.ConvertURLToPDF(url);
            // send the PDF document as a response to the browser for download
            var response = HttpContext.Current.Response;
            response.Clear();
            response.AddHeader("Content-Type", "application/pdf");
            response.AddHeader("Content-Disposition",
                String.Format("attachment; filename=QuoteLetter.pdf; size={0}", pdfBytes.Length));
            response.BinaryWrite(pdfBytes);
            // Note: it is important to end the response, otherwise the ASP.NET
            // web page will render its content to PDF document stream
            response.End();
        }

public static byte[] ConvertURLToPDF(string url)
        {
            // ...redacted for brevity
            byte[] pdfBytes = null;
            var uri = new Uri(url);
            var encryptedParameters = Encrypt(uri.Query);
            var encryptedUrl = uri.Scheme + "://" + uri.Authority + uri.AbsolutePath + "?pid=" + encryptedParameters;
            var htmlData = GetHtmlStringFromUrl(encryptedUrl);
            pdfBytes = pdfConverter.GetPdfBytesFromHtmlString(htmlData);
            return pdfBytes;
        }

 public static string GetHtmlStringFromUrl(string url)
        {
            string htmlData = string.Empty;
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            System.Net.ServicePointManager.ServerCertificateValidationCallback =
        ((sender, certificate, chain, sslPolicyErrors) =>
        {
            return true;
        });
            var response = (HttpWebResponse)request.GetResponse();
                if (response.StatusCode == HttpStatusCode.OK)
                {
                    Stream receiveStream = response.GetResponseStream();
                    StreamReader readStream = null;
                    if (response.CharacterSet == null)
                    {
                        readStream = new StreamReader(receiveStream);
                    }
                    else
                    {
                        readStream = new StreamReader(receiveStream, Encoding.GetEncoding(response.CharacterSet));
                    }
                    htmlData = readStream.ReadToEnd();
                    response.Close();
                    readStream.Close();
                }
            return htmlData;
        }

It’s a lot of code here. btnInvoke_Click is the clearly named “save as PDF button” callback handler. What it does, using ConvertURLToPDF and GetHtmlStringFromUrl is… send a request to a different ASPX file in the same application. It downloads the HTML, and then passes the HTML off to a PDF converter which renders the HTML into a PDF.

For reasons which are unclear, it encrypts the parameters which it's passing in the query string. These requests never go across the network, and even if they did, it's generally more reasonable to pass those parameters in the request body, which would be encrypted via TLS.

And it does send this request using TLS! However, as Keith disabled support for incoming TLS requests, this doesn’t work.

Which is funny, because TLS being disabled is pretty much the only way in which this request could fail. An invalid certificate wouldn’t, because of this callback:

            System.Net.ServicePointManager.ServerCertificateValidationCallback =
        ((sender, certificate, chain, sslPolicyErrors) =>
        {
            return true;
        });

Keith re-enabled TLS throughout the application, “compliance” reasons be damned.

[Advertisement] ProGet can centralize your organization's software applications and components to provide uniform access to developers and servers. Check it out!