I
IIS
Guest
TLDR: It may be because the certificate mappings are saved at the sub-application level, when in fact they are expected at the site or server level.
The setup is simple enough:
Yes, we have set Require SSL with Require Client Certificates for the sub-application.
SSL Settings for the sub-application
Yes, we have disabled the Anonymous authentication for that sub-application.
Authentication settings for the sub-application
Yes, we did install and enable IIS Client Certificate Mapping Authentication, disabling the Client Certificate Mapping Authentication (see the difference in the details section).
Use the Configuration Editor, Section drop-down, selecting system.webServer > security > authentication > clientCertificateMappingAuthentication and iisClientCertificateMappingAuthentication.
Yes, we did add the certificate mappings accordingly, as per documentation.
IIS client certificate mappings for authenticating the requests in the sub-application
And yes, both the server and client certificates are valid, they have their private keys on their respective machines, their usage flags are matching the intended purposes, and they are issued by Certificate Authorities (CAs) trusted by both IIS and client(s).
Symptom
Everything is by the book, the client certificate is valid, has a private key, is trusted by both client and IIS etc. Yet accessing the sub application results in a 401-Unauthorized response:
The authentication error page on the client
Moreover, we take some Failed Request Tracing, and we see that the IIS Web Core module is sending a 401.2 HTTP response status code (a Logon failed due to server configuration, according to HTTP response status codes emitted by IIS).
The Failed Request Tracing (FREB) log illustrating the error
Cause
The immediate cause is that all authentication modules were notified by IIS, but none of them managed to determine a user. The Authenticate stage is over, and we still don’t have a user set. Not even the Anonymous one; which is, of course, expected, since we disabled the Anonymous authentication at the sub-application level. With the User property being NULL, IIS ends the request sending the 401-Unauthorized.
But why didn’t the certificate mapping module determine the user? I mean the certificate is good, everything checks, right?
Well, there is a glitch with the configuration. More exactly, with the location level of where the certificate mappings and configuration are persisted.
We have enabled the IIS Client Certificate Mapping Authentication at the sub-application level, because this is where we needed it; not at the site level.
So the IIS Manager’s Configuration Editor happily obliged, resulting in a configuration like below in applicationHost.config:
NOT GOOD
<configuration>
<location path="Client-Cert-Mapping-IIS-App">
<system.webServer>
<security>
<authentication>
<clientCertificateMappingAuthentication enabled="false" />
</authentication>
<access sslFlags="None" />
</security>
</system.webServer>
</location>
<location path="Client-Cert-Mapping-IIS-App/Sub-Application">
<system.webServer>
<security>
<authentication>
<anonymousAuthentication enabled="false" />
<iisClientCertificateMappingAuthentication enabled="true" manyToOneCertificateMappingsEnabled="false">
<oneToOneMappings>
<add userName="Pingu-IIS.Local-User" password="[enc:IISCngProvider:Spo...Zqk=:enc]" certificate="MIIFwjDwAwggEKAo...O5U=" />
<add userName="Pingu-IIS.ARR-Acnt" password="[enc:IISCngProvider:gha...Yws=:enc]" certificate="MIIFwjDANBgkqhki...9DI=" />
</oneToOneMappings>
</iisClientCertificateMappingAuthentication>
</authentication>
<access sslFlags="Ssl, SslNegotiateCert, SslRequireCert" />
</security>
</system.webServer>
</location>
</configuration>
But according to the documentation for this feature, IIS Client Certificate Mapping Authentication:
The <iisClientCertificateMappingAuthentication> element of the <authentication> element can be configured at the server and site level.
So “at the server and site level”, huh?
The fix
In the above configuration example, it is enough that we move the <iisClientCertificateMappingAuthentication> element at the site level. The site level will still allow Anonymous users, as needed. It will still allow plain HTTP. It will not require a client certificate, but it would be able to map it to an account. This mapping ability will be inherited at sub-application level.
It is only at sub-application level that we disable Anonymous authentication, and we change the SSL/TLS behavior so that a client certificate is requested.
GOOD ONE
<configuration>
<location path="Client-Cert-Mapping-IIS-App">
<system.webServer>
<security>
<authentication>
<clientCertificateMappingAuthentication enabled="false" />
<iisClientCertificateMappingAuthentication enabled="true" manyToOneCertificateMappingsEnabled="false">
<oneToOneMappings>
<add userName="Pingu-IIS.Local-User" password="[enc:IISCngProvider:Spo...Zqk=:enc]" certificate="MIIFwjDwAwggEKAo...O5U=" />
<add userName="Pingu-IIS.ARR-Acnt" password="[enc:IISCngProvider:gha...Yws=:enc]" certificate="MIIFwjDANBgkqhki...9DI=" />
</oneToOneMappings>
</iisClientCertificateMappingAuthentication>
</authentication>
<access sslFlags="None" />
</security>
</system.webServer>
</location>
<location path="Client-Cert-Mapping-IIS-App/Sub-Application">
<system.webServer>
<security>
<authentication>
<anonymousAuthentication enabled="false" />
</authentication>
<access sslFlags="Ssl, SslNegotiateCert, SslRequireCert" />
</security>
</system.webServer>
</location>
</configuration>
Let’s now look at Failed Request Tracing (FREB) log, for a successfully served request.
(I guess this feature should rather be named Request Processing Pipeline Trace, or Pipeline Execution Trace, since you can collect for success too, not only for failures.)
A FREB pipeline execution trace for a successful request
We notice that:
Some comments, details
There is a common misconception on how these modules are working. People think that these modules are performing Certificate Authentication as in “the request user is determined by inspecting the client certificate”. Which is wrong; such functionality, technically possible, would require custom code.
In fact, how the Client Certificate Mapping modules of IIS work a bit different. They would take the client certificate and try to map it to a user account; if successful, that account will be considered as the request user.
Now IIS has 2 modules that are performing the so-called Client Certificate Authentication.
The 2 client certificate mapping features in IIS
They differ in where they look for [certificate <-> account] mappings.
Failing to map the certificate sent by the client to a user account, local or in Active Directory, would mean that we can’t determine the request user from the client certificate.
Other authentication modules, if enabled or so configured, may (or may not) determine the request user.
But keep in mind that after the Authentication stage, in IIS request processing pipeline, we MUST have a user, even if that user is Anonymous (UserName field is empty-string).
If the User field is NULL after the Authentication stage, the IIS Web Core module would stop the request and respond 401-Unauthorized (more precisely, 401.2 = Logon failed due to server configuration).
Continue reading...
The setup is simple enough:
- An IIS site – the root application – where users should be able to access with both HTTPS and plain HTTP, with or without a client certificate, even anonymously. Basically, the default authentication settings at this level plus the HTTPS binding.
- A sub-application that should be protected. Only clients presenting a good, accepted certificate should be able to access this one.
Yes, we have set Require SSL with Require Client Certificates for the sub-application.
SSL Settings for the sub-application
Yes, we have disabled the Anonymous authentication for that sub-application.
Authentication settings for the sub-application
Yes, we did install and enable IIS Client Certificate Mapping Authentication, disabling the Client Certificate Mapping Authentication (see the difference in the details section).
Use the Configuration Editor, Section drop-down, selecting system.webServer > security > authentication > clientCertificateMappingAuthentication and iisClientCertificateMappingAuthentication.
Yes, we did add the certificate mappings accordingly, as per documentation.
IIS client certificate mappings for authenticating the requests in the sub-application
And yes, both the server and client certificates are valid, they have their private keys on their respective machines, their usage flags are matching the intended purposes, and they are issued by Certificate Authorities (CAs) trusted by both IIS and client(s).
Symptom
Everything is by the book, the client certificate is valid, has a private key, is trusted by both client and IIS etc. Yet accessing the sub application results in a 401-Unauthorized response:
The authentication error page on the client
Moreover, we take some Failed Request Tracing, and we see that the IIS Web Core module is sending a 401.2 HTTP response status code (a Logon failed due to server configuration, according to HTTP response status codes emitted by IIS).
The Failed Request Tracing (FREB) log illustrating the error
Cause
The immediate cause is that all authentication modules were notified by IIS, but none of them managed to determine a user. The Authenticate stage is over, and we still don’t have a user set. Not even the Anonymous one; which is, of course, expected, since we disabled the Anonymous authentication at the sub-application level. With the User property being NULL, IIS ends the request sending the 401-Unauthorized.
But why didn’t the certificate mapping module determine the user? I mean the certificate is good, everything checks, right?
Well, there is a glitch with the configuration. More exactly, with the location level of where the certificate mappings and configuration are persisted.
We have enabled the IIS Client Certificate Mapping Authentication at the sub-application level, because this is where we needed it; not at the site level.
So the IIS Manager’s Configuration Editor happily obliged, resulting in a configuration like below in applicationHost.config:
NOT GOOD
<configuration>
<location path="Client-Cert-Mapping-IIS-App">
<system.webServer>
<security>
<authentication>
<clientCertificateMappingAuthentication enabled="false" />
</authentication>
<access sslFlags="None" />
</security>
</system.webServer>
</location>
<location path="Client-Cert-Mapping-IIS-App/Sub-Application">
<system.webServer>
<security>
<authentication>
<anonymousAuthentication enabled="false" />
<iisClientCertificateMappingAuthentication enabled="true" manyToOneCertificateMappingsEnabled="false">
<oneToOneMappings>
<add userName="Pingu-IIS.Local-User" password="[enc:IISCngProvider:Spo...Zqk=:enc]" certificate="MIIFwjDwAwggEKAo...O5U=" />
<add userName="Pingu-IIS.ARR-Acnt" password="[enc:IISCngProvider:gha...Yws=:enc]" certificate="MIIFwjDANBgkqhki...9DI=" />
</oneToOneMappings>
</iisClientCertificateMappingAuthentication>
</authentication>
<access sslFlags="Ssl, SslNegotiateCert, SslRequireCert" />
</security>
</system.webServer>
</location>
</configuration>
But according to the documentation for this feature, IIS Client Certificate Mapping Authentication:
The <iisClientCertificateMappingAuthentication> element of the <authentication> element can be configured at the server and site level.
So “at the server and site level”, huh?
The fix
In the above configuration example, it is enough that we move the <iisClientCertificateMappingAuthentication> element at the site level. The site level will still allow Anonymous users, as needed. It will still allow plain HTTP. It will not require a client certificate, but it would be able to map it to an account. This mapping ability will be inherited at sub-application level.
It is only at sub-application level that we disable Anonymous authentication, and we change the SSL/TLS behavior so that a client certificate is requested.
GOOD ONE
<configuration>
<location path="Client-Cert-Mapping-IIS-App">
<system.webServer>
<security>
<authentication>
<clientCertificateMappingAuthentication enabled="false" />
<iisClientCertificateMappingAuthentication enabled="true" manyToOneCertificateMappingsEnabled="false">
<oneToOneMappings>
<add userName="Pingu-IIS.Local-User" password="[enc:IISCngProvider:Spo...Zqk=:enc]" certificate="MIIFwjDwAwggEKAo...O5U=" />
<add userName="Pingu-IIS.ARR-Acnt" password="[enc:IISCngProvider:gha...Yws=:enc]" certificate="MIIFwjDANBgkqhki...9DI=" />
</oneToOneMappings>
</iisClientCertificateMappingAuthentication>
</authentication>
<access sslFlags="None" />
</security>
</system.webServer>
</location>
<location path="Client-Cert-Mapping-IIS-App/Sub-Application">
<system.webServer>
<security>
<authentication>
<anonymousAuthentication enabled="false" />
</authentication>
<access sslFlags="Ssl, SslNegotiateCert, SslRequireCert" />
</security>
</system.webServer>
</location>
</configuration>
Let’s now look at Failed Request Tracing (FREB) log, for a successfully served request.
(I guess this feature should rather be named Request Processing Pipeline Trace, or Pipeline Execution Trace, since you can collect for success too, not only for failures.)
A FREB pipeline execution trace for a successful request
We notice that:
- Several modules are notified to AUTHENTICATE_REQUEST. But here, only IISCertificateMappingAuthenticationModule is configured to act.
- IISCertificateMappingAuthenticationModule successfully determines a request user, based on the client certificate passed and its configured [certificate <-> account] mappings.
- After IISCertificateMappingAuthenticationModule determines the user, a couple of other modules are notified with same AUTHENTICATE_REQUEST. But none of them would act; they’re not enabled or configured to do so.
- The Authentication stage successfully ends because we do have a user set. Then the Authorize stage begins; the UrlAuthorization module is the first here to be notified with AUTHORIZE_REQUEST.
Some comments, details
There is a common misconception on how these modules are working. People think that these modules are performing Certificate Authentication as in “the request user is determined by inspecting the client certificate”. Which is wrong; such functionality, technically possible, would require custom code.
In fact, how the Client Certificate Mapping modules of IIS work a bit different. They would take the client certificate and try to map it to a user account; if successful, that account will be considered as the request user.
Now IIS has 2 modules that are performing the so-called Client Certificate Authentication.
The 2 client certificate mapping features in IIS
They differ in where they look for [certificate <-> account] mappings.
- The Client Certificate Mapping Authentication would take the certificate sent by the client, and then perform a lookup in the Active Directory. If it finds an account there having that certificate bound to it, then that account will be considered the user of the HTTP request. So the mapping is in Active Directory. I guess this feature should be named Active Directory Client Certificate Mapping Authentication.
- The IIS Client Certificate Mapping Authentication would take the certificate sent by the client, and then perform a lookup in the IIS mappings. So we need to have some mappings defined, in IIS configuration, to resolve a certificate to a user account. These user accounts can be local, defined on the IIS machine, or can be domain user accounts, from Active Directory. But the key is that the [certificate <-> account] mappings are in IIS configuration, not in Active Directory. If an account is found in the mappings to match the client certificate, then that account will be considered the user of the HTTP request.
Failing to map the certificate sent by the client to a user account, local or in Active Directory, would mean that we can’t determine the request user from the client certificate.
Other authentication modules, if enabled or so configured, may (or may not) determine the request user.
But keep in mind that after the Authentication stage, in IIS request processing pipeline, we MUST have a user, even if that user is Anonymous (UserName field is empty-string).
If the User field is NULL after the Authentication stage, the IIS Web Core module would stop the request and respond 401-Unauthorized (more precisely, 401.2 = Logon failed due to server configuration).
Continue reading...