Ignore:
Timestamp:
23/09/09 17:34:01 (10 years ago)
Author:
pjkersha
Message:

Testing SSL Client Authentication middleware with session and redirect middleware to enable wget support for NDG Security.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/ssl.py

    r5751 r5757  
    8989     
    9090 
    91 class ApacheSSLAuthNMiddleware(NDGSecurityMiddlewareBase): 
     91class ApacheSSLAuthnMiddleware(NDGSecurityMiddlewareBase): 
    9292    """Perform SSL peer certificate authentication making use of Apache 
    9393    SSL environment settings 
     
    104104     
    105105    _isSSLRequest = lambda self: self.environ.get( 
    106                                     ApacheSSLAuthNMiddleware.SSL_KEYNAME) == \ 
    107                                     ApacheSSLAuthNMiddleware.SSL_KEYVALUE 
     106                                    ApacheSSLAuthnMiddleware.SSL_KEYNAME) == \ 
     107                                    ApacheSSLAuthnMiddleware.SSL_KEYVALUE 
    108108    isSSLRequest = property(fget=_isSSLRequest, 
    109109                            doc="Is an SSL request boolean - depends on " 
     
    112112    SSL_CLIENT_CERT_KEYNAME = 'SSL_CLIENT_CERT' 
    113113     
     114    # Options for ini file 
     115    RE_PATH_MATCH_LIST_OPTNAME = 'rePathMatchList' 
     116    CACERT_FILEPATH_LIST_OPTNAME = 'caCertFilePathList' 
     117    CLIENT_CERT_DN_MATCH_LIST_OPTNAME = 'clientCertDNMatchList' 
     118     
    114119    propertyDefaults = { 
    115         'clientCertVerificationClassName': None, 
    116         'rePathMatchList': [], 
    117         'caCertFilePathList': [] 
     120        RE_PATH_MATCH_LIST_OPTNAME: [], 
     121        CACERT_FILEPATH_LIST_OPTNAME: [], 
     122        CLIENT_CERT_DN_MATCH_LIST_OPTNAME: [] 
    118123    } 
    119124    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults) 
    120125         
    121126    _isSSLClientCertSet = lambda self: bool(self.environ.get( 
    122                             ApacheSSLAuthNMiddleware.SSL_CLIENT_CERT_KEYNAME))  
     127                            ApacheSSLAuthnMiddleware.SSL_CLIENT_CERT_KEYNAME))  
    123128    isSSLClientCertSet = property(fget=_isSSLClientCertSet, 
    124129                                  doc="Check for client X.509 certificate " 
     
    127132    def __init__(self, app, global_conf, prefix='sslAuthn.', **app_conf): 
    128133         
    129         super(ApacheSSLAuthNMiddleware, self).__init__(app,  
     134        super(ApacheSSLAuthnMiddleware, self).__init__(app,  
    130135                                                       global_conf,  
    131136                                                       prefix=prefix, 
     
    134139        self.__caCertFilePathList = None 
    135140        self.__caCertStack = None 
    136         self.__verifyClientCert = None 
    137          
    138         self.rePathMatchList = [re.compile(r) for r in  
    139                                 app_conf.get('rePathMatchList','').split()] 
    140  
    141         self.caCertStack = app_conf.get('caCertFilePathList', []) 
    142          
    143         # A custom class may be specified to determine what verification to 
    144         # apply to the client certificate 
    145         clientCertVerificationClassName = app_conf.get( 
    146                                     prefix+'clientCertVerificationClassName') 
    147         if clientCertVerificationClassName: 
    148             isClientCertVerificationProperty = lambda i: i[0].startswith( 
    149                                             prefix+'clientCertVerification.') 
    150             clientCertVerificationProperties = \ 
    151                 dict(filter(isClientCertVerificationProperty,app_conf.items())) 
     141        self.__clientCertDNMatchList = None 
     142        self.__clientCert = None 
     143         
     144        rePathMatchListVal = app_conf.get( 
     145                ApacheSSLAuthnMiddleware.RE_PATH_MATCH_LIST_OPTNAME, '') 
     146        self.rePathMatchList = [re.compile(r)  
     147                                for r in rePathMatchListVal.split()] 
     148 
     149        self.caCertStack = app_conf.get( 
     150                ApacheSSLAuthnMiddleware.CACERT_FILEPATH_LIST_OPTNAME, []) 
     151         
     152        self.clientCertDNMatchList = app_conf.get( 
     153                ApacheSSLAuthnMiddleware.CLIENT_CERT_DN_MATCH_LIST_OPTNAME, []) 
    152154                 
    153             self.verifyClientCert = instantiateClass( 
    154                              clientCertVerificationClassName,  
    155                              None,  
    156                              objectType=ClientCertVerificationInterface,  
    157                              classProperties=clientCertVerificationProperties)             
    158         else:  
    159             # Default to carry out no verification 
    160             self.verifyClientCert = NoClientCertVerification() 
    161          
    162155    def _setCACertStack(self, caCertList): 
    163156        '''Read CA certificates from file and add them to an X.509 Cert. 
     
    215208                           doc="CA certificate stack object - " 
    216209                               "peer certificate must validate against one") 
    217      
    218     def _setVerifyClientCert(self, value): 
    219         ''' 
    220         @type value: ClientCertVerificationInterface derived type 
    221         @param value: custom SSL client verification interface 
    222         ''' 
    223          
    224         if not isinstance(value, ClientCertVerificationInterface): 
    225             raise TypeError('Expecting %r type for "verifyClientCert"; got ' 
    226                             '%r' % type(value)) 
    227          
    228         if not callable(value): 
    229             raise TypeError('Expecting callable for "verifyClientCert"') 
    230          
    231         self.__verifyClientCert = value 
    232      
    233     def _getVerifyClientCert(self): 
    234         return self.__verifyClientCert 
    235  
    236     verifyClientCert = property(fset=_setVerifyClientCert, 
    237                                 fget=_getVerifyClientCert, 
    238                                 doc="Client certificate verification " 
    239                                     "interface object") 
     210         
     211    def _setClientCertDNMatchList(self, value): 
     212        '''         
     213        @type value: basestring, list, tuple 
     214        @param value: list of client certificate Distinguished Names as strings 
     215        of X500DN instances''' 
     216         
     217        if isinstance(value, basestring): 
     218            # Try parsing a space separated list of file paths 
     219            self.__clientCertDNMatchList = [X500DN(dn=dn)  
     220                                            for dn in value.split()] 
     221             
     222        elif isinstance(value, (list, tuple)): 
     223            self.__clientCertDNMatchList = [] 
     224            for dn in value: 
     225                if isinstance(dn, basestring): 
     226                    self.__clientCertDNMatchList.append(X500DN(dn=dn)) 
     227                elif isinstance(dn, X500DN): 
     228                    self.__clientCertDNMatchList.append(dn) 
     229                else: 
     230                    raise TypeError('Expecting a string, or %r type for "%s" ' 
     231                                    'list item; got %r' %  
     232                (X500DN, 
     233                 ApacheSSLAuthnMiddleware.CLIENT_CERT_DN_MATCH_LIST_OPTNAME, 
     234                 type(dn))) 
     235                     
     236        else: 
     237            raise TypeError('Expecting a string, list or tuple for "%s"; got ' 
     238                            '%r' %  
     239                (ApacheSSLAuthnMiddleware.CLIENT_CERT_DN_MATCH_LIST_OPTNAME, 
     240                 type(value))) 
     241     
     242    def _getClientCertDNMatchList(self): 
     243        return self.__clientCertDNMatchList 
     244 
     245    clientCertDNMatchList = property(fset=_setClientCertDNMatchList, 
     246                                     fget=_getClientCertDNMatchList, 
     247                                     doc="List of acceptable Distinguished " 
     248                                         "Names for client certificates") 
     249         
     250    def _getClientCert(self): 
     251        return self.__clientCert 
     252 
     253    clientCert = property(fget=_getClientCert, 
     254                          doc="Client certificate for verification set by " 
     255                              "isValidClientCert()") 
    240256     
    241257    @NDGSecurityMiddlewareBase.initCall          
    242258    def __call__(self, environ, start_response): 
    243259        '''Check for peer certificate in environment and if present carry out 
    244         authentication''' 
    245         log.debug("ApacheSSLAuthNMiddleware.__call__ ...") 
     260        authentication 
     261         
     262        @type environ: dict 
     263        @param environ: WSGI environment variables dictionary 
     264        @type start_response: function 
     265        @param start_response: standard WSGI start response function 
     266        ''' 
     267        log.debug("ApacheSSLAuthnMiddleware.__call__ ...") 
    246268         
    247269        if not self._pathMatch(): 
    248             log.debug("ApacheSSLAuthNMiddleware: ignoring path [%s]",  
     270            log.debug("ApacheSSLAuthnMiddleware: ignoring path [%s]",  
    249271                      self.pathInfo) 
    250272            return self._setResponse() 
    251273         
    252274        elif not self.isSSLRequest: 
    253             log.warning("ApacheSSLAuthNMiddleware: 'HTTPS' environment " 
     275            log.warning("ApacheSSLAuthnMiddleware: 'HTTPS' environment " 
    254276                        "variable not found in environment; ignoring request") 
    255277            return self._setResponse() 
    256278                         
    257279        elif not self.isSSLClientCertSet: 
    258             log.error("ApacheSSLAuthNMiddleware: No SSL Client certificate " 
     280            log.error("ApacheSSLAuthnMiddleware: No SSL Client certificate " 
    259281                      "for request to [%s]; setting HTTP 401 Unauthorized",  
    260282                      self.pathInfo) 
     
    267289            return self._setErrorResponse(code=401) 
    268290 
    269              
    270291    def _setResponse(self,  
    271292                     notFoundMsg='No application set for ' 
    272                                  'ApacheSSLAuthNMiddleware', 
     293                                 'ApacheSSLAuthnMiddleware', 
    273294                     **kw): 
    274         return super(ApacheSSLAuthNMiddleware,  
     295        return super(ApacheSSLAuthnMiddleware,  
    275296                     self)._setResponse(notFoundMsg=notFoundMsg, **kw) 
    276297 
    277298    def _setErrorResponse(self, msg='Invalid SSL client certificate', **kw): 
    278         return super(ApacheSSLAuthNMiddleware, self)._setErrorResponse(msg=msg, 
     299        return super(ApacheSSLAuthnMiddleware, self)._setErrorResponse(msg=msg, 
    279300                                                                       **kw) 
    280301 
     
    293314    def isValidClientCert(self): 
    294315        sslClientCert = self.environ[ 
    295                             ApacheSSLAuthNMiddleware.SSL_CLIENT_CERT_KEYNAME] 
    296         x509Cert = X509Cert.Parse(sslClientCert) 
     316                            ApacheSSLAuthnMiddleware.SSL_CLIENT_CERT_KEYNAME] 
     317        self.__clientCert = X509Cert.Parse(sslClientCert) 
    297318         
    298319        if len(self.caCertStack) == 0: 
     
    301322        else: 
    302323            try: 
    303                 self.caCertStack.verifyCertChain(x509Cert2Verify=x509Cert) 
     324                self.caCertStack.verifyCertChain( 
     325                                            x509Cert2Verify=self.__clientCert) 
    304326 
    305327            except X509CertError, e: 
    306328                log.info("Client certificate verification failed with %s " 
    307                          "exception: %s" % (e.__class__, e)) 
     329                         "exception: %s" % (type(e), e)) 
    308330                return False 
    309331             
    310332            except Exception, e: 
    311333                log.error("Client certificate verification failed with " 
    312                           "unexpected exception type %s: %s" % (e.__class__,e)) 
     334                          "unexpected exception type %s: %s" % (type(e), e)) 
    313335                return False 
    314          
    315         # Check certificate Distinguished Name via  
    316         # ClientCertVerificationInterface object 
    317         return self.verifyClientCert(x509Cert) 
    318  
     336             
     337        if len(self.clientCertDNMatchList) > 0: 
     338            if self.__clientCert.dn not in self.clientCertDNMatchList: 
     339                return False 
     340             
     341        return True 
     342#         
     343#        # Check certificate Distinguished Name via  
     344#        # ClientCertVerificationInterface object 
     345#        return self.verifyClientCert(x509Cert) 
     346     
     347 
     348class AuthKitSSLAuthnMiddleware(ApacheSSLAuthnMiddleware): 
     349    """Update REMOTE_USER AuthKit environ key with certificate CommonName to 
     350    flag logged in status to other middleware 
     351    """ 
     352    @NDGSecurityMiddlewareBase.initCall          
     353    def __call__(self, environ, start_response): 
     354        '''Check for peer certificate in environment and if present carry out 
     355        authentication.  Overrides parent class behaviour to set REMOTE_USER 
     356        AuthKit environ key based on the client certificate's Distinguished  
     357        Name CommonName field.  If no certificate is present or it is  
     358        present but invalid no 401 response is set.  Instead, it is left to 
     359        the following middleware in the chain to deal with this.  When used 
     360        in conjunction with  
     361        ndg.security.server.wsgi.openid.relyingparty.OpenIDRelyingPartyMiddleware, 
     362        this will result in the display of the Relying Party interface but with 
     363        a 401 status set. 
     364         
     365        @type environ: dict 
     366        @param environ: WSGI environment variables dictionary 
     367        @type start_response: function 
     368        @param start_response: standard WSGI start response function 
     369        ''' 
     370        if not self._pathMatch(): 
     371            log.debug("AuthKitSSLAuthnMiddleware: ignoring path [%s]",  
     372                      self.pathInfo) 
     373 
     374        elif not self.isSSLRequest: 
     375            log.warning("AuthKitSSLAuthnMiddleware: 'HTTPS' environment " 
     376                        "variable not found in environment; ignoring request") 
     377                         
     378        elif not self.isSSLClientCertSet: 
     379            log.debug("AuthKitSSLAuthnMiddleware: no client certificate set - " 
     380                      "passing request to next middleware in the chain ...") 
     381             
     382        elif self.isValidClientCert(): 
     383            # Update environ so that downstream AuthenticationMiddleware can 
     384            # set the session cookie 
     385            self.environ['REMOTE_USER'] = self.clientCert.dn['CN'] 
     386             
     387            # Set-up redirect back to original request URI 
     388        else: 
     389            # IsValidCert will log warnings/errors no need to flag this 
     390            # condition 
     391            pass 
     392             
     393        # Pass request to next middleware in the chain without setting an 
     394        # error response - see method doc string for explanation. 
     395        return self._setResponse() 
Note: See TracChangeset for help on using the changeset viewer.