Web Connection
smartcard auth
Gravatar is a globally recognized avatar based on your email address. smartcard auth
  Lauren Clarke
  All
  Jul 28, 2016 @ 12:27pm

Hello WWWC team, long time.

We have some WWWC v6 apps running on a client's domain that may need to be placed behind smartcard (PIV) access. Server is Win2012R2. Anyone walk this path before? I realize this is more if an IIS question than WWWC, but that's our specific context here. Any links/whitepapers etc appreciated.

-lc

Gravatar is a globally recognized avatar based on your email address. re: smartcard auth
  Rick Strahl
  Lauren Clarke
  Jul 28, 2016 @ 11:44pm

Let us know what you find. I've not done anything with this but this might be interesting to integrate in the future...

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: smartcard auth
  Lauren Clarke
  Rick Strahl
  Jun 6, 2017 @ 07:36am

Finally getting back to this. Seems we may need to have some new server vars parsed by wc.dll to get at the x.509 vars we need. Is there a way to configure wc.ini to find and parse additional properties from the cert? Am particularly interested in the Subject Alternative Name at this point.

Am trying to mimic this sort of thing (aspx):

<%@ Page Language="C#" Trace="false" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ Import Namespace="System.Security.Cryptography.X509Certificates" %>
<%@ Import Namespace="System.Security.Cryptography" %>

<script runat="server">
    //protected void Page_Load(object sender, EventArgs e)
    //{ }

    void LoadCertInfo()
    {
        string para = "<div style='margin: 10px 0 0 0; font-weight: bold'>{0}</div>";
        string subpara = "<div style='margin-left: 15px; font-size: 90%'>{0}</div>";

        if (Page.Request.ClientCertificate.IsPresent)
        {
            Response.Write("<hr /><div style='width: 500px; margin: 20px auto'>");
            Response.Write("<h3 style='width: 500px; margin: 20px auto'>Client Certificate Information</h3>");
            try
            {
                X509Certificate2 x509Cert2 = new X509Certificate2(Page.Request.ClientCertificate.Certificate);

                Response.Write(string.Format(para, "Issued To:"));
                Response.Write(string.Format(subpara, x509Cert2.Subject));

                Response.Write(string.Format(para, "Issued By:"));
                Response.Write(string.Format(subpara, x509Cert2.Issuer));

                Response.Write(string.Format(para, "Valid Till:"));
                Response.Write(string.Format(subpara, "To: " + x509Cert2.GetExpirationDateString()));

                #region Subject Alternative Name Section
                X509Extension sanExtension = (X509Extension)x509Cert2.Extensions["Subject Alternative Name"];
                if (sanExtension != null)
                {
                    Response.Write(string.Format(para, "Subject Alternative Name:"));
                    Response.Write(string.Format(subpara, sanExtension.Format(true)));

                }
                else
                {
                    Response.Write(string.Format(para, "No Subject Alternative Name Data"));
                }

                #endregion // Subject Alternative Name Section


            }
            catch (Exception ex)
            {
                Response.Write(string.Format(para, "An error occured:"));
                Response.Write(string.Format(subpara, ex.Message));
                Response.Write(string.Format(subpara, ex.StackTrace));
            }
            finally
            {
                Response.Write("</div>");
            }
        }
      else
        {
        Response.Write("no PIV card present");
        }
    }
</script>
<html>
  <head runat="server">
    <title><% Page.Response.Write(System.Environment.MachineName); %></title>
  </head>
  <body>
      <% LoadCertInfo();  %>
  </body>
</html>

Gravatar is a globally recognized avatar based on your email address. re: smartcard auth
  Rick Strahl
  Lauren Clarke
  Jun 6, 2017 @ 11:00am

If you're using the .NET Module you're getting all the server vars. With wc.dll you have to add the server vars explicitly in the configuration.

[Extra Server Variables]
Var1=LOCAL_ADDR
Var2=APPL_MD_PATH

Just add whatever additional server vars you need to include to the list.

Post them here too, and I can add them to wc.dll explicitly.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: smartcard auth
  Lauren Clarke
  Rick Strahl
  Jun 8, 2017 @ 05:41pm

Ok, thanks for the reply. So, I fired up a .net module instance and found three additional vars that were not present in the ISAPI side:

Var4=CERT_ISSUER Var5=CERT_KEYSIZE Var6=CERT_SECRETKEYSIZE

Interestingly, both keysizes come back empty from wc.dll, whereas they are integers on the .net side. Not sure if it's a typing issue or if 4 is the max number of extras you allow in the .ini. But, it would be nice to have CERT_ISSUER under ISAPI if you're offering...

However there are lots more in the spec. The thing I'm really interested in is the SAN (Subject Alternative Name) which is not present on the .net module side, and I expect takes a little more decoding to extract from the cert (see the aspx sample above). Since I can't use .net module in this project, it seems like the easiest thing would be either:

  1. extract san inside wc.dll (probably more C++ work than you want at the moment) or

  2. arrange for wc.dll to provide the raw client cert string (and we'll parse it VFP, perhaps with wwDotNetBridge, or something else).

  3. is more work (for us) but it offers the potential to get at any of the x509 standard vars, and also to walk the chain to find trusted orgs (necessary in a multi-org scenario)

I was hopeful when I saw wwrequest.getClientCertificate() but disappointed to find that it only returns (via unscoped local) CERT_SUBJECT at most. Something like you get in classic .asp with Convert.ToBase64String(Page.Request.ClientCertificate.Certificate); would be expected I think. Maybe a new wwRequest.GetRAWClientCertificate() ?

*********************************************************************
FUNCTION GetClientCertificate(lcSubKey)
***************************************

lcCert = THIS.ServerVariables("CERT_SUBJECT")
IF !EMPTY(lcSubkey)
   DO CASE
      CASE UPPER(lcSubkey) = "NAME"
         lcSubKey = "CN"
      CASE UPPER(lcSubKey) = "EMAIL"         
         lcSubKey = "E"
   ENDCASE
   
   lcCert = ALLTRIM(Extract(lcCert,", "+lcSubkey + "=",", ",,.T.) )
ENDIF

RETURN lcCert
ENDFUNC
* EOF wwRequest::GetClientCertificate
Gravatar is a globally recognized avatar based on your email address. re: smartcard auth
  Rick Strahl
  Lauren Clarke
  Jun 10, 2017 @ 08:33pm

So I took a look. It looks like the server var

These are the SSL values I get (from the .NET module):

CERT_COOKIE=
CERT_FLAGS=
CERT_ISSUER=
CERT_KEYSIZE=256
CERT_SECRETKEYSIZE=2048
CERT_SERIALNUMBER=
CERT_SERVER_ISSUER=C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3
CERT_SERVER_SUBJECT=CN=west-wind.com
CERT_SUBJECT=

I've added CERT_SERVER_ISSUER and CERT_SERVER_SUBJECT as well as the keysize ones, to the data processed by default. We should get the same data in ISAPI with that like .NET.

What really sucks is that ISAPI doesn't have a way that I know of to just pick up all the server variables in raw form. I'm sure there is a raw buffer with all that info somewhere, but not sure how to get access to it 😃 Docs were never any good 😦

Either way - certificate info wouldn't be in the data anyway - IIS is doing a certificate lookup. I suppose you can do the same using wwDotnetBridge, but you would most definitely want to cache this as it's slow to do the cert lookup.

But I gotta ask. Is client certificates still a thing? Why not just throw on a LetsEncrypt public certificate and have this just be encrypted without the hassles of client cert dealings.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: smartcard auth
  Rick Strahl
  Lauren Clarke
  Jun 10, 2017 @ 10:48pm

So played with this a little and you can do this:

loBridge = GetwwDotnetBridge()

LOCAL loValue as Westwind.WebConnection.ComValue
loValue = loBridge.CreateComValue()
loValue.SetEnum("System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine")

*** Open the Store
loStore = loBridge.CreateInstance("System.Security.Cryptography.X509Certificates.X509Store","WebHosting",loValue)

*** Open it
loValue.SetEnum("System.Security.Cryptography.X509Certificates.OpenFlags.ReadOnly")
? loBridge.InvokeMethod(loStore,"Open",loValue)

*** .NET Collection - turn to ComArray
loStores = loBridge.Createarray()
loStores.FromEnumerable(loStore.Certificates)
? "Count: " + TRANSFORM(loStores.Count)

*** Get the certificates
FOR lnX = 0 TO loStores.Count -1
     loCert = loStores.Item(lnX)
     ? loCert.Subject + " - " + loCert.FriendlyName + " - " + loCert.ThumbPrint
          
     TRY
	     lcSan = loBridge.InvokeMethod(loCert,'Extensions["Subject Alternative Name"].Format',.T.)
    	 ? lcSan
     CATCH
     ENDTRY
ENDFOR

loStore.Close()

RETURN

The trick is figuring out where you'll look up the certificate.

Here I'm using the "WebHosting" store where IIS stores it's self assigned certificates. More commonly certs live in the "MY" store though. You just need to point it at the right store and then filter.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: smartcard auth
  Lauren Clarke
  Rick Strahl
  Jun 11, 2017 @ 10:10pm

Thanks Rick really appreciate you taking a look. I'm feeling my way here, just poking around to see if there is a path before turning this over to someone far more capable.

Couple things:

  • this is only incidentally about encryption, we are doing 2FA with PIV card + pin
  • So, what i'm really needing is the raw client cert passed from IIS. (maybe it's in the ALL_RAW var? It's available in classic asp, as shown above...)

However, with one major caveat (below), I think I can probably skate by with the vars you've provided in a single org scenario which is all I need for the moment.

As I understand it, if I have the org id and the card's serial number, I'll have something unique that I can trust so I can map that to the user in some sort of pre-registration process, and then allow them application access without un/pw, (they just provide their card's pin client side).

The SAN is a NTH for validation of the mapping process (perhaps even automating it) as the SAN often contains the user's email address which would help us map to their account in the initial setup. But, we could do that by other means too I think.

My present noob question is this: If I don't get to have the raw client cert, and am going to use the server vars provided by wc.dll instead, how do I know the header vars I see (eg: CERT_ISSUER) are actually trusted? I haven't tried it, but what's stopping someone from spoofing those vars in http header that wc.dll provides? Will wc.dll set them null/empty if the cert flag from IIS is not 1?

Gravatar is a globally recognized avatar based on your email address. re: smartcard auth
  Rick Strahl
  Lauren Clarke
  Jun 12, 2017 @ 11:30am

You can't spoof server variables - you can only spoof client headers.

IIS will not put those Cert server vars in the request data if the cert is not valid. In fact you're unlikely to get to the page if the cert is not valid, so I don't think that's a concern. The idea is that IIS validates your trust for you. I don't think you're expected to validate the certificates yourself - that's error prone and likely to lead to security issues. That's the job of the infrastructure.

+++ Rick ---

© 1996-2024