home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Writing Apache Modules with Perl and C
By:   Lincoln Stein and Doug MacEachern
Published:   O'Reilly & Associates, Inc.  - March 1999

Copyright © 1999 by O'Reilly & Associates, Inc.


 


   Show Contents   Previous Page   Next Page

Chapter 6 - Authentication and Authorization / Authentication with the Secure Sockets Layer
Using Digital Certificates for Authorization

The SSL protocol does most of its work at a level beneath the workings of the HTTP protocol. The exchange and verificaton of digital certificates and the establishment of the encrypted channel all occur before any of Apache's handlers run. For this reason, authorization based on the contents of a digital certificate looks quite different from the other examples we've seen in this chapter. Furthermore, the details of authorization vary slightly among the different implementations of ApacheSSL. This section describes the way it works in Ralf S. Engelschall's mod_ssl. If you are using a different version of ApacheSSL, you should check your vendor's documentation for differences.

The text representation of a typical client certificate is shown in Example 6-15. It consists of a "Subject" section, which gives information on the person to whom the certificate is issued, and a "Certificate" section, which gives information about the certificate itself. Within the Subject section are a series of tag=value pairs. There can be an arbitrary number of such pairs, but several are standard and can be found in any certificate:

CN
User's common name
EMail
User's email address
O
User's organization (employer)
OU
Organizational unit (e.g., department)
L
User's locality, usually a city or town
SP
User's state or province
C
User's country code

The user's distinguished name (DN) is a long string consisting of the concatenation of each of these fields in the following format:

/C=US/SP=MA/L=Boston/O=Capricorn Organization/OU=Sales/CN=Wanda/Email=wanda@capricorn.com

European users will recognize the footprints of the OSI standards committee here. The DN is guaranteed to be unique among all the certificates issued by a particular certificate-granting authority.

The Certificate section contains the certificate's unique serial number and other data, followed by more tag=value pairs giving information about the organization issuing the certificate. The standard fields are the same as those described for the Subject. This is followed by a Validity period, which gives the span of time that the certificate should be considered valid.

You are free to use any of these fields for authorization. You can authorize based on the user's CN field, on the certificate's serial number, on the Validity period, on the DN, or on any of the Subject or Issuer tags.

The certificate information is actually stored in a compact binary form rather than the text form shown here. When the connection is established, the SSL library parses out the certificate fields and stores them in a private data structure. During the fixup phase, these fields are turned into various environment variables with names like SSL_CLIENT_S_DN_CN (to be read as "the common name subfield of the distinguished name of the subject section of the client's certificate"). However, the mappings between certificate field and environment variable differ from version to version of ApacheSSL, and you will have to check your vendor's documentation for the details.

Example 6-15. An Example Client Certificate

Subject:
    C=US
    SP=MA
    L=Boston
    O=Capricorn Organization
    OU=Sales
    CN=Wanda
    Email=wanda@capricorn.com
Certificate:
   Data:
      Version: 1 (0x0)
      Serial Number: 866229881 (0x33a19e79)
      Signature Algorithm: md5WithRSAEncryption 
Issuer: C=US SP=MA L=Boston O=Capricorn Consulting OU=Security Services CN=Capricorn Signing Services Root CA Email=lstein@capricorn.com Validity: Not Before: Jun 13 19:24:41 1998 GMT
Not After : Jun 13 19:24:41 1999 GMT

The most straightforward way to authenticate based on certificate information is to take advantage of the SSLRequire access control directive. In mod_ssl, such a directive might look like this:

<Location /certified>
  SSLRequire  %{SSL_CLIENT_S_DN_CN} in ("Wanda Henderson","Joe Bloe") \
              and %{REMOTE_ADDR} =~ m/^192\.128\.3\.[0-9]+$/
</Location>

This requires that the CN tag of the DN field of the Subject section of the certificate match either "Wanda Henderson" or "Joe Bloe", and that the browser's IP address satisfy a pattern match placing it within the 192.128.3 subnetwork. mod_ssl has a rich language for querying the contents of the client certificate. See its documentation for the details. Other ApacheSSL implementations also support operations similar to SSLRequire, but they differ somewhat in detail.

Note that to Apache, SSLRequire is an access control operation rather than an authentication/authorization operation. This is because no action on the part of the user is needed to gain access--his browser either has the right certificate, or it doesn't.

A slightly more involved technique for combining certificate information with user authorization is to take advantage of the FakeBasicAuth option of the SSLOptions directive. When this option is enabled, mod_ssl installs an authentication handler that retrieves the DN from the certificate. The handler base64-encodes the DN and a hardcoded password (consisting of the string "password"), stuffs them into the incoming Authorization header field, and returns DECLINED. In effect, this fakes the ordinary Basic authentication process by making it seem as if the user provided a username and password pair. The DN is now available for use by downstream authentication and authorization modules, where it appears as the username.

However, using FakeBasicAuth means that mod_ssl must be the first authentication handler run for the request and that an authentication handler further down the chain must be able to authenticate using the client's DN. It is much simpler to bypass all authentication handlers and obtain of the DN by using a subrequest. This takes advantage of the fact that during the fixup phase, mod_ssl places parsed copies of the certificate fields into the subprocess environment table, preparatory to copying them into a CGI script's environment.

As an example, we'll show a simple authorization module named Apache::AuthzSSL which checks that a named field of the DN name matches that given in one or more require directives. A typical configuration section will look like this:

SSLVerifyClient require
SSLVerifyDepth 2
SSLCACertificateFile  conf/ssl.crt/ca-bundle.crt
<Directory /usr/local/apache/htdocs/ID/please>
   SSLRequireSSL
   AuthName SSL
   AuthType Basic
   PerlAuthenHandler Apache::OK
   PerlAuthzHandler  Apache::AuthzSSL
   require  C US
   require  O "Capricorn Organization"
   require OU Sales Marketing
</Directory>

The SSLVerifyClient directive, which must be present in the main part of the configuration file, requires that browsers present client certificates. The SSLVerifyDepth and SSLCACertificateFile directives are used to configure how deeply mod_ssl should verify client certificates (see the mod_ssl documentation for details). The SSLRequireSSL directive requires that SSL be active in order to access the contents of this directory.

AuthName and AuthType are not required, since we are not performing Basic authentication, but we put them in place just in case some module downstream is expecting them. Since the password is invariant when client certificate verification is in use, we bypass password checking by installing Apache::OK as the authentication handler for this directory.11Apache::OK is a ring module that exits with an OK result code. We then install Apache::AuthzSSL as the authorization handler and give it three different require statements to satisfy. We require that the country field equal "US," the organization field equal "Capricorn Organization," and the organizational unit be one of "Sales" or "Marketing."

Example 6-16 gives the code for Apache::AuthzSSL. It brings in Apache::Constants and the quotewords() text parsing function from the standard Text::ParseWords module. It recovers the request object and calls its requires() method to retrieve the list of authorization requirements that are in effect.

The handler then issues a subrequest and retrieves the value of SSL_CLIENT_DN from the subrequest's environment table. The subrequest is necessary because the parsed certificate fields aren't placed into the table until the fixup stage, which ordinarily occurs after the authorization phase. Notice that the handler returns OK if is_main() returns false, avoiding infinite recursion during the subrequest. Once the DN is recovered, it is split into its individual fields using a pattern match operation.

Now the routine loops through each of the requirements, breaking them into a DN field name and a list of possible values, each of which it checks in turn. If none of the specified values matches the DN, we log an error and return a FORBIDDEN (not an AUTH_REQUIRED) status code. If we satisfy all the requirements and fall through to the bottom of the loop, we return an OK result code.

Example 6-16. Authorizing Clients Based On Their Digital Certificate's DN

package Apache::AuthzSSL;
use strict;
use Apache::Constants qw(:common);
use Text::ParseWords  qw(quotewords);
sub handler {
   my $r = shift;
   return OK unless $r->is_main;
    my $requires = $r->requires;
   return DECLINED unless $requires;
    my $subr = $r->lookup_uri($r->uri);
   my $dn = $subr->subprocess_env('SSL_CLIENT_S_DN');
   return DECLINED unless $dn;
   my(%dn) = $dn =~ m{/([^=]+)=([^/]+)}g;
  REQUIRES:
   for my $entry (@$requires) {
      my($field, @values) = quotewords('\s+', 0, $entry->{requirement});
      foreach (@values) {
          next REQUIRES if $dn{$field} eq $_;
       }
       $r->log_reason("user $dn{CN}: not authorized", $r->filename);
       return FORBIDDEN;
   }
   # if we get here, then we passed all the requirements
   return OK;
}
1;
__END__

The only subtlety in this module is the rationale for returning FORBIDDEN in an authorization module rather than in the more typical note_basic_auth_failure() call followed by AUTH_REQUIRED. The reason for this is that returning AUTH_REQUIRED will set in motion a chain of events that will ultimately result in the user being prompted for a username and password. But there's nothing the user can type in to satisfy this module's requirements, so this is just a tease. Returning FORBIDDEN, in contrast, will display a more accurate message denying the user permission to view the page.

A more advanced certificate authorization module would probably go to a database to determine whether the incoming certificate satisfied the requirements.

As another example, Example 6-17 shows a small access handler that rejects all certificates issued by out-of-state issuers. It does so by looking at the value of the subprocess variable SSL_CLIENT_I_DN_SP, which returns the issuer's state or province code. This handler can be installed with a configuration section like this one:

SSLVerifyClient require
<Location /government/local>
   SSLRequireSSL
   PerlAccessHandler Apache::CheckCertState
   PerlSetVar  IssuerState Maryland
</Location>

The code simply retrieves the contents of the IssuerState configuration variable and the SSL_CLIENT_I_DN_SP subprocess environment variable. If either is undefined, the handler returns DECLINED. Next the handler checks whether the two variables are equal, and if so, returns OK. Otherwise the routine returns FORBIDDEN, displaying the "access denied" message on the user's browser.

Example 6-17. Apache::CheckCertState Checks the SP (State/Province) Field of the Certificate Issuer

package Apache::CheckCertState;
# file: Apache/CheckCertState.pm
use Apache::Constants qw(:common);
sub handler {
   my $r = shift;
   return DECLINED unless $r->is_main;
   my $state = $r->dir_config('IssuerState');
   return DECLINED unless defined $state;
   my $subr = $r->lookup_uri($r->uri);
   my $client_state = $subr->subprocess_env('SSL_CLIENT_I_DN_SP') || "";
   return OK if $client_state eq $state;
   return FORBIDDEN;
}
1;
__END__

We hope this chapter has given you some idea of the range and versatility of Apache modules for controlling who can gain access to your site and what they do once they've connected. With the tools and examples presented in this chapter as a starting point, you should be able to implement almost any access control system you can imagine.

The next chapter turns to some of the more esoteric handlers and module function-ality, showing you a variety of techniques for simplifying Apache administration and customizing the server's behavior.

11 Apache::OK is always available, along with Apache::DECLINED, since they are imported from Apache::Constants by Apache.pm at server startup time.

   Show Contents   Previous Page   Next Page
Copyright © 1999 by O'Reilly & Associates, Inc.