Changeset 4051 for TI12-security/trunk


Ignore:
Timestamp:
18/07/08 15:31:55 (11 years ago)
Author:
pjkersha
Message:

Working version of ElementTree WS-Security SignatureHandler?:

  • test code in security/python/Tests/etreewss
  • uses elementtree-1.3b1-20080615-badc with patch:

Index: elementtree/ElementC14N.py
===================================================================
--- elementtree/ElementC14N.py (revision 3438)
+++ elementtree/ElementC14N.py (working copy)
@@ -230,10 +231,11 @@

# attributes
attrib = []
for k, v in sorted(elem.attrib.items()):

  • prefix, uri, k = qname(elem, k)
  • if prefix:
  • namespaces[prefix] = uri
  • attrib.append((k, v))

+ if k[:6] != "xmlns:":
+ prefix, uri, k = qname(elem, k)
+ if prefix:
+ namespaces[prefix] = uri
+ attrib.append((k, v))

# explicitly included namespaces
if nsinclude:

if nsmap:

  • More changes needed to ElementTreeProxy? to mimic DOM behaviour for ParsedSOAP and TC and TCcompound - added ability to add a separate pseudo test node. Nb. the current test is only a simple WSDL. More validation work required vs. Attribute Authority and Session Manager interfaces.
  • needs testing against DOM implementation of C14N
Location:
TI12-security/trunk/python
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/Tests/etreewss/server/echoServer.py

    r4038 r4051  
    146146        return 
    147147 
    148     sw = SoapWriter(nsdict=nsdict, writerclass=ElementTreeProxy) 
     148    sw = SoapWriter(nsdict=nsdict, outputclass=ElementTreeProxy) 
    149149    try: 
    150150        sw.serialize(result) 
     
    250250    echo.signatureHandler = SignatureHandler(cfgFilePath=wsseCfgFilePath) 
    251251 
     252    # Fudge to correct default unsupressedPrefixes setting from  
     253    # WSSecurityConfig 
     254    echo.signatureHandler.refC14nKw = {'inclusive_namespaces':[]} 
     255    echo.signatureHandler.signedInfoC14nKw = {'inclusive_namespaces':[]} 
     256     
    252257    serviceContainer.setNode(echo, url=path) 
    253258    serviceContainer.RequestHandlerClass = ElementTreeSOAPRequestHandler 
  • TI12-security/trunk/python/ndg.security.common/ndg/security/common/wssecurity/etree.py

    r4045 r4051  
    6363from ndg.security.common.wssecurity import WSSecurityConfig 
    6464 
     65# Check SoapWriter.dom attribute is ElementTreeProxy type 
     66from ndg.security.common.zsi_utils.elementtreeproxy import ElementTreeProxy 
     67 
    6568from ndg.security.common.X509 import X509Cert, X509CertParse, X509CertRead, \ 
    6669X509Stack, X509StackParseFromDER 
     
    166169    binSecTokEncType = \ 
    167170"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" 
     171 
     172    # Namespaces for XPath searches 
     173    _processorNSs = \ 
     174    { 
     175        'ds':       DSIG.BASE,  
     176        'wsu':      WSU.UTILITY,  
     177        'wsse':     OASIS.WSSE,  
     178        'SOAP-ENV': SOAP.ENV  
     179#        'soapenv':  SOAP.ENV 
     180    } 
     181 
    168182 
    169183    #_________________________________________________________________________ 
     
    765779         
    766780 
    767     def _verifyTimeStamp(self, parsedSOAP, ctxt): 
     781    def _verifyTimeStamp(self, parsedSOAP, timestampMustBeSet=False): 
    768782        """Call from verify to check timestamp if found.   
    769783         
     
    773787        @param parsedSOAP: object contain parsed SOAP message received from 
    774788        sender 
    775         @type ctxt: 
    776         @param ctxt: XPath context object""" 
    777  
    778         try: 
    779             timestampNode = xpath.Evaluate('//wsse:Timestamp', 
    780                                            contextNode=parsedSOAP.dom, 
    781                                            context=ctxt)[0] 
    782         except: 
    783             log.warning("Verifying message - No timestamp element found") 
    784             return 
     789        @type timestampMustBeSet: bool 
     790        @param timestampMustBeSet: set to True to raise an exception if no 
     791        timestamp element is present in the message to be checked""" 
     792 
     793        timestampElems = self._soapEnvElem.findall('.//wsu:Timestamp',  
     794                                                namespaces=self._processorNSs) 
     795        nTimestampElems = len(timestampElems)         
     796        if nTimestampElems > 1: 
     797            raise TimestampError("Expecting only one timestamp element") 
     798         
     799        elif nTimestampElems == 0: 
     800            msg = "Verifying message - No timestamp element found" 
     801            if timestampMustBeSet: 
     802                raise VerifyError(msg) 
     803            else: 
     804                log.warning(msg) 
     805                return 
     806         
     807        timestampElem = timestampElems[0] 
    785808         
    786809        # Time now  
    787810        dtNow = datetime.utcnow() 
    788811         
    789         createdNode = timestampNode.getElementsByTagName("Created") 
    790          
    791         # Workaround for fractions of second 
     812        createdElem = timestampElem.find("wsu:Created",  
     813                                         namespaces=self._processorNSs) 
     814        if createdElem is None: 
     815            raise TimestampError("No 'Created' element found in timestamp") 
     816 
     817        # Workaround for fractions of second with datetime module 
    792818        try: 
    793             [createdDateTime, createdSecFraction]=createdNode.nodeValue.split() 
     819            createdDateTime,strCreatedSecFraction = createdElem.text.split('.') 
     820            strCreatedSecFraction = strCreatedSecFraction.split('Z')[0] 
     821            createdExp = -int(len(strCreatedSecFraction)) 
     822            createdSecFraction = int(strCreatedSecFraction) * 10 ** createdExp 
     823             
    794824        except ValueError, e: 
    795825            raise ValueError("Parsing timestamp Created element: %s" % e) 
    796826         
     827 
    797828        dtCreated = datetime.strptime(createdDateTime, '%Y-%m-%dT%H:%M:%S') 
    798         dtCreated += timedelta(seconds=int(createdSecFraction)) 
     829        dtCreated += timedelta(seconds=createdSecFraction) 
    799830        if dtCreated >= dtNow: 
    800831            raise TimestampError(\ 
     
    802833                (dtCreated, dtNow)) 
    803834         
    804         expiresNode = timestampNode.getElementsByTagName("Expires") 
    805         if expiresNode is None: 
    806             log.warning(\ 
     835        expiresElem = timestampElem.find("wsu:Expires", 
     836                                         namespaces=self._processorNSs) 
     837        if expiresElem is None: 
     838            raise TimestampError(\ 
    807839                "Verifying message - No Expires element found in Timestamp") 
    808840            return 
    809841 
    810842        try: 
    811             [expiresDateTime, expiresSecFraction]=expiresNode.nodeValue.split() 
     843            expiryDateTime, strExpirySecFraction = expiresElem.text.split('.') 
     844            strExpirySecFraction = strExpirySecFraction.split('Z')[0] 
     845            expiryExp = -int(len(strExpirySecFraction)) 
     846            expirySecFraction = int(strExpirySecFraction) * 10 ** expiryExp 
    812847        except ValueError, e: 
    813848            raise ValueError("Parsing timestamp Expires element: %s" % e) 
    814849         
    815         dtCreated = datetime.strptime(expiresDateTime, '%Y-%m-%dT%H:%M:%S') 
    816         dtCreated += timedelta(seconds=int(createdSecFraction)) 
    817         if dtExpiry > dtNow: 
     850        dtExpiry = datetime.strptime(expiryDateTime, '%Y-%m-%dT%H:%M:%S') 
     851        dtExpiry += timedelta(seconds=expirySecFraction) 
     852        if dtExpiry < dtNow: 
    818853            raise TimestampError(\ 
    819                 "Timestamp expiry time %s is after the current time %s" % \ 
     854                "Timestamp expiry time %s is before the current time %s" % \ 
    820855                (dtCreated, dtNow)) 
     856             
     857        log.debug("Verified timestamp") 
    821858             
    822859                    
     
    829866        ''' 
    830867         
    831         # Namespaces for XPath searches 
    832         processorNSs = \ 
    833         { 
    834             'ds':       DSIG.BASE,  
    835             'wsu':      WSU.UTILITY,  
    836             'wsse':     OASIS.WSSE,  
    837             'SOAP-ENV': SOAP.ENV  
    838         } 
    839  
     868        # Check for expected soapWriter DOM class 
     869        if not isinstance(soapWriter.dom, ElementTreeProxy): 
     870            raise SignatureError("Expecting ElementTreeProxy type for " 
     871                                 "ZSI.writer.SoapWriter.dom instance") 
     872             
    840873        # Add X.509 cert as binary security token 
    841874        # TODO: sub encodestring with b64encode? 
     
    876909        # Check <wsse:security> isn't already present in header 
    877910        wsseElems = self._soapEnvElem.findall('.//wsse:security', 
    878                                               namespaces=processorNSs) 
     911                                              namespaces=self._processorNSs) 
    879912        if len(wsseElems) > 1: 
    880913            raise SignatureError('wsse:Security element is already present') 
     
    9791012        # Pick up all the wsu:Id tagged elements set in the above 
    9801013        refElems = self._soapEnvElem.findall('.//*[@wsu:Id]', 
    981                                              namespaces=processorNSs) 
     1014                                             namespaces=self._processorNSs) 
    9821015 
    9831016        # 1) Reference Generation 
     
    9971030                                                  **self.__refC14nKw) 
    9981031            log.debug('Canonicalisation for URI "%s": %s', uri, refC14n) 
    999             import pdb;pdb.set_trace() 
    10001032             
    10011033            # Calculate digest for reference and base 64 encode 
     
    10731105        sender""" 
    10741106 
    1075         processorNSs = \ 
    1076         { 
    1077             'ds':       DSIG.BASE,  
    1078             'wsu':      WSU.UTILITY,  
    1079             'wsse':     OASIS.WSSE,  
    1080             'soapenv':  SOAP.ENV 
    1081         } 
     1107        if not isinstance(parsedSOAP.dom, ElementTreeProxy): 
     1108            raise VerifyError("Expecting ElementTreeProxy type for " 
     1109                              "ZSI.parse.ParsedSoap.dom") 
    10821110         
    10831111        self._soapEnvElem = parsedSOAP.dom._elem 
    1084 #        soapHdrElem = parsedSOAP._header._elem 
    1085 #        soapBodyElem = parsedSOAP.body._elem 
    10861112 
    10871113        signatureElems = self._soapEnvElem.findall('.//ds:Signature',  
    1088                                                    namespaces=processorNSs)         
     1114                                                   namespaces=self._processorNSs)         
    10891115        if len(signatureElems) > 1: 
    10901116            raise VerifyError('Multiple <ds:Signature/> elements found') 
     
    11071133        # the transforms elements 
    11081134        c14nMethodElem = self._soapEnvElem.find('.//ds:CanonicalizationMethod',  
    1109                                                 namespaces=processorNSs) 
     1135                                                namespaces=self._processorNSs) 
    11101136        if c14nMethodElem is None: 
    11111137            raise VerifyError("No <ds:Canonicalization/> element found")   
    11121138              
    11131139        refElems = self._soapEnvElem.findall('.//ds:Reference',  
    1114                                              namespaces=processorNSs) 
     1140                                             namespaces=self._processorNSs) 
     1141        if len(refElems) == 0: 
     1142            raise VerifyError("No <ds:Reference/> elements found")   
     1143         
    11151144        for refElem in refElems: 
    11161145            # Get the URI for the reference 
     
    11181147                          
    11191148            transformElem = refElem.find('ds:Transforms/ds:Transform', 
    1120                                          namespaces=processorNSs) 
     1149                                         namespaces=self._processorNSs) 
    11211150            if transformElem is None: 
    11221151                raise VerifyError( 
     
    11311160                # Check for no inclusive namespaces set 
    11321161                inclusiveNSElem = transformElem.find("InclusiveNamespaces", 
    1133                                                 namespaces=processorNSs)                     
     1162                                                namespaces=self._processorNSs)                     
    11341163                if inclusiveNSElem is not None: 
    11351164                    pfxListTxt = inclusiveNSElem.get('PrefixList') 
     
    11511180            # XPath reference 
    11521181            uriXPath = './/*[@wsu:Id="%s"]' % refURI[1:] 
    1153             uriElem=self._soapEnvElem.findall(uriXPath,namespaces=processorNSs) 
     1182            uriElem=self._soapEnvElem.findall(uriXPath,namespaces=self._processorNSs) 
    11541183            if len(uriElem) > 1: 
    1155                 raise VerifyError("Multiple elements matching '%s' " % \ 
    1156                                   uriXPath + \ 
    1157                                   "search path: %s" % uriElem) 
    1158                  
    1159             refC14n=parsedSOAP.dom.canonicalize(subset=uriElem[0],**refC14nKw) 
     1184                raise VerifyError("Multiple elements matching '%s' search"  
     1185                                  " path: %s" % (uriXPath, uriElem)) 
     1186 
     1187            refC14n=parsedSOAP.dom.canonicalize(subset=uriElem[0], **refC14nKw) 
    11601188             
    11611189            # encodestring adds line delimiters at 76 char intervals - avoid  
     
    11641192             
    11651193            # Extract the digest value that was stored in the SOAP request          
    1166             digestElem = refElem.find('ds:DigestValue',namespaces=processorNSs) 
     1194            digestElem = refElem.find('ds:DigestValue',namespaces=self._processorNSs) 
    11671195            if digestElem is None: 
    11681196                raise VerifyError('Failed to get digestValue for ' \ 
     
    11771205                log.error('Canonicalisation for URI: "%s": %s' % \ 
    11781206                          (refURI, refC14n)) 
    1179                 import pdb;pdb.set_trace() 
    11801207                raise InvalidSignature('Digest Values do not match for URI:' \ 
    11811208                                       ' "%s"' % refURI) 
    11821209             
    1183             log.debug("Verified canonicalization for element %s" % refURI[1:]) 
    1184              
    1185         else: 
    1186             raise VerifyError("No <ds:Reference/> elements found")   
    1187  
     1210            log.debug("Verified canonicalization for element '%s'"%refURI[1:]) 
     1211             
    11881212 
    11891213        # 2) Signature Validation 
    11901214        signedInfoElem = self._soapEnvElem.find('.//ds:SignedInfo', 
    1191                                                 namespaces=processorNSs) 
     1215                                                namespaces=self._processorNSs) 
    11921216        if signedInfoElem is None: 
    11931217            raise VerifyError("No <ds:signedInfo/> section found") 
     
    12061230            # Check for no inclusive namespaces set 
    12071231            inclusiveNSElem = c14nMethodElem.find("InclusiveNamespaces", 
    1208                                                   namespaces=processorNSs)                     
     1232                                                namespaces=self._processorNSs)                     
    12091233            if inclusiveNSElem is not None: 
    12101234                pfxListTxt = inclusiveNSElem.get('PrefixList') 
     
    12261250        # Get the signature value in order to check against the digest just 
    12271251        # calculated 
    1228         signatureValueElem = self._soapEnvElem.find('//ds:SignatureValue', 
    1229                                                     namespaces=processorNSs) 
     1252        signatureValueElem = self._soapEnvElem.find('.//ds:SignatureValue', 
     1253                                                namespaces=self._processorNSs) 
    12301254 
    12311255        # Remove base 64 encoding 
     
    12461270        # elsewhere 
    12471271        binSecTokElem = self._soapEnvElem.find('.//wsse:BinarySecurityToken', 
    1248                                                namespaces=processorNSs)         
     1272                                               namespaces=self._processorNSs)         
    12491273        if binSecTokElem is not None: 
    12501274            x509CertTxt = str(binSecTokElem.text) 
     
    12921316            raise InvalidSignature("Invalid signature") 
    12931317         
     1318        log.debug("Verified signature") 
     1319         
    12941320        # Verify chain of trust  
    12951321        x509Stack.verifyCertChain(x509Cert2Verify=self.__verifyingCert, 
    12961322                                  caX509Stack=self.__caX509Stack) 
    1297          
    1298         self._verifyTimeStamp(parsedSOAP, ctxt)  
     1323 
     1324        log.debug("Verified certificate chain of trust") 
     1325         
     1326        self._verifyTimeStamp(parsedSOAP)  
     1327 
    12991328        log.info("Signature OK")         
    13001329 
  • TI12-security/trunk/python/ndg.security.common/ndg/security/common/zsi_utils/elementtreeproxy.py

    r4045 r4051  
    2323    _soap_enc_nsuri = SOAP.ENC 
    2424 
    25     def __init__(self, message=None, rootElem=None, elem=None): 
     25    def __init__(self, message=None, rootElem=None, elem=None, text=None): 
    2626        '''Initialize''' 
    2727        self._elem = elem 
     
    2929        self._etree = None 
    3030        self._rootETree = None 
     31         
     32        # Pseudo text node - it has a text attribute only 
     33        self._text = text 
     34        if text is not None and self._elem is not None: 
     35            raise ValueError("elem keyword must not be set for a text node") 
    3136         
    3237        # Flag to enable correct behaviour for DOM childNodes 
     
    163168        # for a parsed message but not true for a document created in memory. 
    164169        # In the latter case a call to build the scope is required 
    165         if hasattr(self._etree, '_scope'): 
     170        if hasattr(self._rootETree, '_scope'): 
    166171            ElementC14N.write(self._rootETree, f, **kw) 
    167172        else: 
     
    192197        fInput.seek(0) 
    193198         
    194         self._etree = ElementC14N.parse(fInput) 
    195         self._elem = self._etree.getroot() 
     199        self._rootETree = ElementC14N.parse(fInput) 
     200        self._rootElem = self._rootETree.getroot() 
     201         
     202        self._etree = self._rootETree 
     203        self._elem = self._rootElem 
     204         
     205        # Enables behaviour to mimic DOM interface - See _getChildNodes 
    196206        self._treeRootSet = True 
     207 
    197208        return self 
    198209     
     
    208219            # the top level element will be returned instead 
    209220            return [ElementTreeProxy(elem=self._elem, rootElem=self._rootElem)] 
     221        elif self._elem is None: 
     222            # A text node 
     223            return [] 
    210224        else: 
    211             return [ElementTreeProxy(elem=elem, rootElem=self._rootElem) \ 
    212                     for elem in list(self._elem)] 
     225            etProxyList=[ElementTreeProxy(elem=elem, rootElem=self._rootElem) \ 
     226                         for elem in list(self._elem)] 
     227             
     228            # DOM treats text as a separate element - if text is set make a 
     229            # new ElementProxy instance and append it to the list to be  
     230            # returned 
     231            if self._elem.text is not None: 
     232                etProxyList += [ElementTreeProxy(text=self._elem.text,  
     233                                                 rootElem=self._rootElem)] 
    213234                 
     235            return etProxyList 
     236           
    214237    childNodes = property(fget=_getChildNodes) 
     238 
     239    def _getNodeValue(self): 
     240        '''Mimic nodeValue attribute of DOM interface''' 
     241        return unicode(self._text)         
     242         
     243    nodeValue = property(fget=_getNodeValue) 
    215244     
    216245    def _getNodeType(self): 
    217246        '''Minimal implementation to mimic behaviour of xml.dom.Node for  
    218247        ParsedSoap interface''' 
    219         return Node.ELEMENT_NODE 
     248        if self._text is not None: 
     249            return Node.TEXT_NODE 
     250        else: 
     251            return Node.ELEMENT_NODE 
    220252     
    221253    nodeType = property(fget=_getNodeType) 
     
    223255    def _getLocalName(self): 
    224256        '''Parse localName from element tag of form {NS}localName''' 
    225         return self._elem.tag.split('}')[-1] 
     257        return unicode(self._elem.tag.split('}')[-1]) 
    226258     
    227259    localName = property(fget=_getLocalName) 
     
    229261    def _getNamespaceURI(self): 
    230262        '''Parse NS from element tag of form {NS}localName''' 
    231         return self._elem.tag.replace('{','').split('}')[0] 
     263        return unicode(self._elem.tag.replace('{','').split('}')[0]) 
    232264     
    233265    namespaceURI = property(fget=_getNamespaceURI) 
     
    237269        are not included in ET 
    238270         
    239         TODO: Use attrib Element attribute + get xmlns declarations from 
    240         _scope attribute.  Returning [] to ParsedSoap satisifes the interface 
    241         for doc creation but is only a temp fix ''' 
    242         return [] 
     271        TODO: fix prefix - possible with _scope look-up, NamedNodeMap is not 
     272        used - dict is used as an approximation''' 
     273        namedNodeMap = {} 
     274 
     275        if self._elem is None: 
     276            return namedNodeMap 
     277         
     278        prefix = u'' 
     279        for k, v in self._elem.attrib.items(): 
     280            localName = self.localName 
     281            qName = prefix + u':' + localName 
     282            nameNodeMap[qName] = Attr(qName,  
     283                                      namespaceURI=self.namespaceURI, 
     284                                      localName=localName) 
     285        return namedNodeMap 
    243286     
    244287    attributes = property(fget=_getAttributes) 
Note: See TracChangeset for help on using the changeset viewer.