No Certificate in OnAuthorization(HttpActionContext actionContext)

丶灬走出姿态 提交于 2019-12-24 15:16:40

问题


I have created CertificateTestController and ValuesController from this example How to use a client certificate to authenticate and authorize in a Web API. If you scroll down to "Update" from user Ogglas. I have taken his example and gotten "CertificateTestController" to work where I can grab the Certificate from my store and add it to the "handler". When I call "ValuesController", there is no cert being initialized by

X509Certificate2 cert = actionContext.Request.GetClientCertificate();

Here is the complete code that I have

ValuesController

{
    [RequireSpecificCert]
    public class ValuesController : ApiController
    {
        // GET api/values
        public IHttpActionResult Get()
        {
            return Ok("It works!");
        }

        public class RequireSpecificCertAttribute : AuthorizationFilterAttribute
        {
            public override void OnAuthorization(HttpActionContext actionContext)
            {

                if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
                {
                    actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
                    {
                        ReasonPhrase = "HTTPS Required"
                    };
                }
                else
                {
                    X509Certificate2 cert = actionContext.Request.GetClientCertificate();
                    X509Certificate2 cert2 = actionContext.RequestContext.ClientCertificate;

                    if (cert == null)
                    {
                        actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
                        {
                            ReasonPhrase = "Client Certificate Required"
                        };
                    }
                    else
                    {
                        X509Chain chain = new X509Chain();

                        //Needed because the error "The revocation function was unable to check revocation for the certificate" happened to me otherwise
                        chain.ChainPolicy = new X509ChainPolicy()
                        {
                            RevocationMode = X509RevocationMode.NoCheck,
                        };
                        try
                        {
                            var chainBuilt = chain.Build(cert);
                            Debug.WriteLine(string.Format("Chain building status: {0}", chainBuilt));

                            var validCert = CheckCertificate(chain, cert);

                            if (chainBuilt == false || validCert == false)
                            {
                                actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
                                {
                                    ReasonPhrase = "Client Certificate not valid"
                                };
                                foreach (X509ChainStatus chainStatus in chain.ChainStatus)
                                {
                                    Debug.WriteLine(string.Format("Chain error: {0} {1}", chainStatus.Status, chainStatus.StatusInformation));
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            Debug.WriteLine(ex.ToString());
                        }
                    }

                    base.OnAuthorization(actionContext);
                }
            }

            private bool CheckCertificate(X509Chain chain, X509Certificate2 cert)
            {
                var rootThumbprint = WebConfigurationManager.AppSettings["rootThumbprint"].ToUpper().Replace(" ", string.Empty);

                var clientThumbprint = WebConfigurationManager.AppSettings["clientThumbprint"].ToUpper().Replace(" ", string.Empty);

                //Check that the certificate have been issued by a specific Root Certificate
                var validRoot = chain.ChainElements.Cast<X509ChainElement>().Any(x => x.Certificate.Thumbprint.Equals(rootThumbprint, StringComparison.InvariantCultureIgnoreCase));

                //Check that the certificate thumbprint matches our expected thumbprint
                var validCert = cert.Thumbprint.Equals(clientThumbprint, StringComparison.InvariantCultureIgnoreCase);

                return validRoot && validCert;
            }
        }

calling above ValuesController with below CertificateTestController

{
    [RoutePrefix("api/certificatetest")]
    public class CertificateTestController : ApiController
    {
        public IHttpActionResult Get()
        {
            var handler = new WebRequestHandler();
            handler.ClientCertificateOptions = ClientCertificateOption.Manual;
            handler.ClientCertificates.Add(GetClientCert());
            handler.UseProxy = false;
            var client = new HttpClient(handler);
            var result = client.GetAsync("https://localhost:44301//values").GetAwaiter().GetResult();
            var resultString = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
            return Ok(resultString);
        }

        private static X509Certificate GetClientCert()
        {
            X509Store store = null;
            try
            {
                store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
                store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);

                var certificateSerialNumber = "2bc034466b6960d2fee84d86e6c2532a".ToUpper().Replace(" ", string.Empty);

                var cert = store.Certificates.Cast<X509Certificate>().FirstOrDefault(x => x.GetSerialNumberString().Equals(certificateSerialNumber, StringComparison.InvariantCultureIgnoreCase));
                return cert;
            }
            finally
            {
                store.Close();
            }
        }
    }
}

Please help!


回答1:


Here are the issues/problems I answered along the way when I was trying to fix this issue.

Q1 .

Why wasn't my certificate getting to the client side (code)?

A1.

VS does a initial SSL negotiation that happens before the OnAuthorization(HttpActionContext actionContext) is even hit. At that point, server searches for a client certificate in the certificate store that has a private key installed to verify. If there is no private key then it fails. I found the issue by turning on the verbose. Please see below.

Here are configurations that need to be changes in order to get this working.

1. Web Config file changes

• SSL Negotiation

Below configuration tells visual studio that there is a ssl negotiation expected for “api/values” URL. This helps us limit when and where is certificate negotiation is expected. Location path property

<location path="api/values">
  <system.webServer>
     <security>
        <access sslFlags="SslNegotiateCert" />
        <authentication>
          <iisClientCertificateMappingAuthentication enabled="true">
          </iisClientCertificateMappingAuthentication>
        </authentication>
      </security>
    </system.webServer>
  </location>

• Certificate Verbose

Below diagnostics helps us during troubleshooting and spits out any issues that might be causing with certificates validation.

<system.diagnostics>
    <trace autoflush="true" />
    <sources>
      <source name="System.Net">
        <listeners>
          <add name="System.Net"/>
        </listeners>
      </source>
      <source name="System.Net.HttpListener">
        <listeners>
          <add name="System.Net"/>
        </listeners>
      </source>
      <source name="System.Net.Sockets">
        <listeners>
          <add name="System.Net"/>
        </listeners>
      </source>
      <source name="System.Net.Cache">
        <listeners>
          <add name="System.Net"/>
        </listeners>
      </source>
    </sources>
    <sharedListeners>
      <add name="System.Net"
       type="System.Diagnostics.TextWriterTraceListener"
       initializeData="System.Net.trace.log"
       traceOutputOptions = "ProcessId, DateTime"/>
    </sharedListeners>
    <switches>
      <add name="System.Net" value="Verbose" />
      <add name="System.Net.Sockets" value="Verbose" />
      <add name="System.Net.Cache" value="Verbose" />
      <add name="System.Net.HttpListener" value="Verbose" />
    </switches>
  </system.diagnostics>

• App settings

Change rootThumbprint value to whatever server certificates’s thumbprint is and clientThumprint would be whatever client certificate thumbprint is. ceritificateSerialNumber should be outgoing certificate serial number.

<appSettings>
            <add key="webpages:Version" value="3.0.0.0" />
            <add key="webpages:Enabled" value="false" />
            <add key="ClientValidationEnabled" value="true" />
            <add key="UnobtrusiveJavaScriptEnabled" value="true" />
            <add key="rootThumbprint" value="change"/>
            <add key="clientThumbprint" value="change"/>
<add key="certificateSerialNumber" value="change"/>
</appSettings>

2. Local Visual Studio and IIS express

1) Applicationhost.config

 Path Visual studio earlier than 2015 but after 2008 Location --> c:\Users\e#\Documents\IISExpress\Config Visual Studio 2015 & 2017 {project_name}.vs\config\config.host.config
 Changes Make sure below properties are set to “Allow”

2) Enable SSL

Enable SSL in the visual studio properties window . this is important because URL changes from http to https.

3) WebApiConfig.cs

Create a class filters folder. Name it appropriately. Certificate validation needs to be the very first check before any of the other code is ran. This is where WebApiConfig.cs comes in handy. For example I called my class RequireHttpsAttribute. In order to run the check just put the below line in WebApiConfig.cs config.Filters.Add(new RequireHttpsAttribute());

4) CertificateTestController.cs

This is class acts as a client. This class is used to attach the cert with the request and send it out. There is one change in this class. This is was only tested locally on the computer. var result = client.GetAsync("https://localhost:44300//api//values").GetAwaiter().GetResult(); Change the URL. We are attaching the certificate based off of serial number below. Certificate can also be attached based off of Thumprint, Subject, Expiration/Revocation, Chain Validation etc.

var certificateSerialNumber = WebConfigurationManager.AppSettings["certificateSerialNumber"].ToUpper().Replace(" ", string.Empty);

5) ValueController.cs

This class acts as a server. This is where the certificate validation happens. There are two changes in “CheckCertificate” method/function that refers to the webApiConfig.cs. if you followed App Settings changes from above under WebApiConfig.cs then you are good to go.

3. Files

1) CertificateTestController.cs

2) ValuesController.cs



来源:https://stackoverflow.com/questions/54948695/no-certificate-in-onauthorizationhttpactioncontext-actioncontext

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!