Changeset 1959


Ignore:
Timestamp:
05/01/07 15:12:15 (14 years ago)
Author:
pjkersha
Message:

python/ndg.security.test/ndg/security/test/XMLSecDoc/xmlSecDocTest.py,
python/ndg.security.common/ndg/security/common/XMLSec.py: working version for enveloped signature and
equivalent verification. Needs test vs. pyXMLSec code as an independent check.

Location:
TI12-security/trunk/python
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/ndg.security.common/ndg/security/common/XMLSec.py

    r1953 r1959  
    3232from xml.dom.ext.reader.PyExpat import Reader 
    3333from Ft.Xml.Domlette import NonvalidatingReader 
     34 
     35# Use to find parent node when parsing docs 
     36from xml.dom.Element import Element 
     37 
     38getParentNode = lambda docNode: [elem for elem in docNode.childNodes \ 
     39                                 if isinstance(elem, Element)][0] 
    3440 
    3541# Digest and signature/verify 
     
    294300 
    295301    #_________________________________________________________________________ 
    296     def sign(self, 
    297              xmlTxt=None, 
    298              inclX509Cert=True, 
    299              **c14nKw): 
    300         """Sign XML document using an X.509 certificate private key 
     302    def applyEnvelopedSignature(self, 
     303                        xmlTxt=None, 
     304                        inclX509Cert=True, 
     305                        refC14nKw={'unsuppressedPrefixes': ['xmlns', 'ns1']}, 
     306                        signedInfoC14nKw={'unsuppressedPrefixes': ['ds']}): 
     307         
     308        """Make enveloped signature of XML document 
    301309 
    302310        @param xmlTxt:          string buffer containing xml to be signed. If 
     
    308316                                certificate.  This can be used by the  
    309317                                recipient of the XML in order to verify the 
    310                                 message""" 
     318                                message 
     319         
     320        @param refC14nKw:       Keywords for canonicalization of the reference 
     321                                - for enveloped type signature this is the 
     322                                parent element of the XML document""" 
    311323 
    312324        if xmlTxt: 
     
    317329                            "XML to be signed has not been read in or parsed." 
    318330 
    319         childNode = self.__docNode.childNodes[1] 
    320         childNode.setAttributeNS(XMLNS.BASE, 'xmlns:%s' % 'ds', DSIG.BASE) 
    321         childNode.setAttributeNS(XMLNS.BASE, 'xmlns:%s'%'ec', DSIG.C14N_EXCL) 
     331        try: 
     332            parentNode = getParentNode(self.__docNode) 
     333        except Exception, e: 
     334            raise VerifyError, "Locating parent node: " + str(e) 
     335 
     336        parentNode.setAttributeNS(XMLNS.BASE, 'xmlns:%s' % 'ds', DSIG.BASE) 
     337        parentNode.setAttributeNS(XMLNS.BASE, 'xmlns:%s'%'ec', DSIG.C14N_EXCL) 
    322338         
    323339        # Serialize and re-parse prior to reference generation - calculating 
     
    332348            'ec':    DSIG.C14N_EXCL 
    333349        } 
    334         ctxt = Context(self.__docNode, processorNss=processorNss) 
     350        ctx = Context(self.__docNode, processorNss=processorNss) 
    335351         
    336352        # 1) Reference Generation 
    337         # 
    338         # Find references 
    339         if not c14nKw: 
    340             c14nKw['unsuppressedPrefixes'] = ['xmlns', 'ns1'] 
    341          
    342         # Canonicalize reference 
    343         refC14n = Canonicalize(childNode, **c14nKw) 
     353         
     354        # Canonicalize reference - for enveloped signature this is the parent 
     355        # element 
     356        refC14n = Canonicalize(parentNode, **refC14nKw) 
    344357         
    345358        # Calculate digest for reference and base 64 encode 
     
    352365        signatureNode = self.__docNode.createElementNS(DSIG.BASE, 
    353366                                                       'ds:Signature') 
    354         childNode.appendChild(signatureNode) 
     367        parentNode.appendChild(signatureNode) 
    355368 
    356369         
     
    361374         
    362375        # Signed Info - Canonicalization method 
    363         signedInfoC14nKw = {} 
    364         signedInfoC14nKw['unsuppressedPrefixes'] = ['ds'] 
    365          
    366376        c14nMethodNode = self.__docNode.createElementNS(DSIG.BASE, 
    367377                                                'ds:CanonicalizationMethod')         
     
    394404 
    395405 
    396         # Add a new reference element to SignedInfo 
     406        # Add a new reference element to SignedInfo - URI is set to null 
     407        # indicating enveloped signature used 
    397408        refNode = self.__docNode.createElementNS(DSIG.BASE, 'ds:Reference') 
     409        refNode.setAttribute('URI', "") 
    398410        signedInfoNode.appendChild(refNode) 
    399411         
     
    420432                                                       'InclusiveNamespaces') 
    421433        inclNamespacesNode.setAttribute('PrefixList', 
    422                                     ' '.join(c14nKw['unsuppressedPrefixes'])) 
     434                                ' '.join(refC14nKw['unsuppressedPrefixes'])) 
    423435        transformNode.appendChild(inclNamespacesNode) 
    424436         
     
    440452 
    441453        # 2) Signature Generation 
    442         c14nSignedInfo = Canonicalize(signedInfoNode, **signedInfoC14nKw) 
     454        signedInfoC14n = Canonicalize(signedInfoNode, **signedInfoC14nKw) 
    443455 
    444456        # Calculate digest of SignedInfo 
    445         signedInfoDigestValue = sha(c14nSignedInfo).digest().strip() 
     457        calcSignedInfoDigestValue = sha(signedInfoC14n).digest().strip() 
    446458         
    447459        # Read Private key to sign with     
     
    451463         
    452464        # Sign using the private key and base 64 encode the result 
    453         signatureValue = priKey.sign(signedInfoDigestValue) 
     465        signatureValue = priKey.sign(calcSignedInfoDigestValue) 
    454466        b64EncSignatureValue = base64.encodestring(signatureValue).strip() 
    455467 
     
    486498 
    487499    #_________________________________________________________________________ 
    488     def verify(self, xmlTxt=None): 
    489         """Verify XML signature in file.  Returns True if valid otherwise 
    490         False. 
     500    def verifyEnvelopedSignature(self, xmlTxt=None): 
     501        """Verify enveloped signature of XML document.  Raises  
     502        InvalidSignature exception if the signature is invalid 
    491503 
    492504        xmlTxt:                 string buffer containing the text from the XML 
     
    499511                                by self.__filePath is read instead. 
    500512 
    501         certFilePathList:       Certificate used to sign the document. 
    502                                 """ 
     513        certFilePathList:       Certificate used to sign the document.""" 
     514 
     515        # Check Certificate files for read access 
     516        if not self.__certFilePathList:                 
     517            raise VerifyError, "No certificate files set for check" 
     518         
     519        if xmlTxt: 
     520            self.parse(xmlTxt) 
     521                                 
    503522        if self.__docNode is None: 
    504523            raise XMLSecDocError, \ 
    505524                            "verify signature: no document has been parsed" 
    506525 
    507         # Check Certificate files for read access 
    508         if not self.__certFilePathList:                 
    509             raise XMLSecDocError, "No certificate files set for check" 
    510          
    511         if xmlTxt: 
    512             self.parse(xmlTxt) 
    513  
    514         import pdb;pdb.set_trace()  
    515  
     526        try: 
     527            parentNode = getParentNode(self.__docNode) 
     528        except Exception, e: 
     529            raise VerifyError, "Locating parent node: " + str(e) 
    516530         
    517531        processorNss = \ 
     
    520534            'ec':    DSIG.C14N_EXCL 
    521535        } 
    522         ctxt = Context(self.__docNode, processorNss=processorNss) 
     536        ctx = Context(self.__docNode, processorNss=processorNss) 
    523537         
    524538 
    525539        signatureNodes = xpath.Evaluate('//ds:Signature',  
    526540                                        contextNode=self.__docNode,  
    527                                         context=ctxt) 
     541                                        context=ctx) 
    528542        if len(signatureNodes) > 1: 
    529543            raise VerifyError, 'Multiple ds:Signature elements found' 
    530544         
    531545        try: 
    532             signatureNodes = signatureNodes[0] 
     546            signatureNode = signatureNodes[0] 
    533547        except: 
    534548            # Message wasn't signed 
    535549            return 
    536550         
    537         # Two stage process: reference validation followed by signature  
    538         # validation  
    539          
    540         # 1) Reference Validation 
     551        # Extract all information required from the Signature node first and  
     552        # then remove it so that the reference digest may be calculated.  This 
     553        # is necessary as for enveloped signature the digest was calculated  
     554        # prior to the addition of the Signature node 
    541555         
    542556        # Check for canonicalization set via ds:CanonicalizationMethod - 
    543557        # Use this later as a back up in case no Canonicalization was set in  
    544558        # the transforms elements 
    545         c14nMethodNode = xpath.Evaluate('//ds:CanonicalizationMethod',  
    546                                         contextNode=self.__docNode,  
    547                                         context=ctxt)[0] 
     559        try: 
     560            c14nMethodNode = xpath.Evaluate('//ds:CanonicalizationMethod',  
     561                                            contextNode=self.__docNode,  
     562                                            context=ctx)[0] 
     563        except Exception, e: 
     564            raise VerifyError, "CanonicalizationMethod element not found: " +\ 
     565                                str(e) 
    548566         
    549567        refNodes = xpath.Evaluate('//ds:Reference',  
    550568                                  contextNode=self.__docNode,  
    551                                   context=ctxt) 
    552         if len(refNodes) > 1: 
     569                                  context=ctx) 
     570        if len(refNodes) != 1: 
    553571            raise VerifyError, \ 
    554                 "Expecting only one reference element for enveloped signature" 
     572                    "Expecting one reference element for enveloped signature" 
    555573         
    556574        refNode = refNodes[0] 
    557575         
     576        # Check for reference URI set 
     577        refURIattrNode = refNode.getAttributeNodeNS(None, 'URI') 
     578        if refURIattrNode and refURIattrNode.value: 
     579            raise VerifyError, "Reference URI value is expected to be " + \ 
     580                               "null for enveloped type signature" 
     581         
     582         
     583        # Get transforms that were applied 
    558584        try: 
    559585            transformsNode = getElements(refNode, "Transforms")[0] 
    560586            transforms = getElements(transformsNode, "Transform") 
    561587 
    562             refAlgorithms = [tfm.getAttributeNodeNS(None, "Algorithm").value \ 
    563                              for tfm in transforms] 
    564588        except Exception, e: 
    565589            raise VerifyError,'failed to get transform algorithm: %s' % str(e) 
    566590             
    567         # Add extra keyword for Exclusive canonicalization method 
     591             
     592        # Check for enveloped style signature and also check for list of  
     593        # namespaces to be excluded if Exclusive canonicalization method was 
     594        # specified 
    568595        refC14nKw = {} 
     596        envelopedAlgorithmSet = False 
     597         
    569598        for transform in transforms: 
    570599            refAlgorithm=transform.getAttributeNodeNS(None, "Algorithm").value 
    571600            if refAlgorithm == DSIG.C14N_EXCL: 
    572601                try: 
    573                     inclusiveNS = getElements(transform,  
    574                                               "InclusiveNamespaces") 
     602                    inclusiveNSnode = getElements(transform,  
     603                                              "InclusiveNamespaces")[0] 
    575604                     
    576                     pfxListAttNode = inclusiveNS[0].getAttributeNodeNS(None,  
     605                    pfxListAttNode = inclusiveNSnode.getAttributeNodeNS(None,  
    577606                                                               'PrefixList') 
    578                     c14nKw['unsuppressedPrefixes'] = \ 
     607                    refC14nKw['unsuppressedPrefixes'] = \ 
    579608                                                pfxListAttNode.value.split() 
    580609                    break 
    581610                except Exception, e: 
    582                     raise VerifyError, \ 
    583                 'failed to parse exclusive canonicalisation transform: %s' %  
    584                     str(e) 
    585      
    586         # Canonicalize the reference data and calculate the digest 
    587         refC14n = Canonicalize(uriNode, **refC14nKw) 
    588         digestValue = base64.encodestring(sha(refC14n).digest()).strip() 
    589          
    590         # Extract the digest value that was stored             
    591         digestNode = getElements(refNode, "DigestValue")[0] 
    592         nodeDigestValue = str(digestNode.childNodes[0].nodeValue).strip()    
    593          
    594         # Reference validates if the two digest values are the same 
    595         if digestValue != nodeDigestValue: 
     611                    raise VerifyError, 'Failed to parse exclusive ' + \ 
     612                                    'canonicalisation transform: %s' % str(e) 
     613            elif refAlgorithm == DSIG.ENVELOPED: 
     614                envelopedAlgorithmSet = True 
     615         
     616         
     617        if not envelopedAlgorithmSet: 
    596618            raise VerifyError, \ 
    597                     'Digest Values do not match for reference data' 
    598                  
    599         # 2) Signature Validation 
    600         signedInfoNode = xpath.Evaluate('//ds:SignedInfo', 
    601                                         contextNode=self.__docNode,  
    602                                         context=ctxt)[0] 
    603  
    604         #import pdb;pdb.set_trace() 
     619            "Expecting enveloped type signature to be specified in transform" 
     620         
     621         
     622        # Extract the digest value for the reference  
     623        try:            
     624            refDigestNode = getElements(refNode, "DigestValue")[0] 
     625            refDigestValue=str(refDigestNode.childNodes[0].nodeValue).strip() 
     626             
     627        except Exception, e: 
     628            raise VerifyError, "Error reading reference digest value" 
     629         
     630 
     631        try: 
     632            signedInfoNode = xpath.Evaluate('//ds:SignedInfo', 
     633                                            contextNode=self.__docNode,  
     634                                            context=ctx)[0] 
     635        except Exception, e: 
     636            raise VerifyError, "Error reading SignedInfo element: " + str(e) 
     637 
     638 
    605639        # Get algorithm used for canonicalization of the SignedInfo  
    606640        # element.  Nb. This is NOT necessarily the same as that used to 
     
    615649                 
    616650                pfxListAttNode = inclusiveNS[0].getAttributeNodeNS(None,  
    617                                                            'PrefixList') 
     651                                                               'PrefixList') 
    618652                signedInfoC14nKw['unsuppressedPrefixes'] = \ 
    619                                             pfxListAttNode.value.split() 
     653                                                pfxListAttNode.value.split() 
    620654            except Exception, e: 
    621                 raise VerifyError, \ 
    622             'failed to handle exclusive canonicalisation for SignedInfo: %s'%\ 
    623                         str(e) 
    624  
    625         # Canonicalize the SignedInfo node and take digest 
    626         c14nSignedInfo = Canonicalize(signedInfoNode, **signedInfoC14nKw)         
    627         signedInfoDigestValue = sha(c14nSignedInfo).digest() 
     655                raise VerifyError, 'failed to handle exclusive ' + \ 
     656                                'canonicalisation for SignedInfo: %s' % str(e) 
    628657         
    629658        # Get the signature value in order to check against the digest just 
    630659        # calculated 
    631         signatureValueNode = xpath.Evaluate('//ds:SignatureValue', 
    632                                             contextNode=self.__docNode,  
    633                                             context=ctxt)[0] 
    634  
    635         # Remove base 64 encoding 
    636         b64EncSignatureValue = \ 
     660        try: 
     661            signatureValueNode = xpath.Evaluate('//ds:SignatureValue', 
     662                                                contextNode=self.__docNode,  
     663                                                context=ctx)[0]     
     664            # Remove base 64 encoding 
     665            b64EncSignatureValue = \ 
    637666                    str(signatureValueNode.childNodes[0].nodeValue).strip() 
     667        except Exception, e: 
     668            raise VerifyError, "Error reading signatureValue: " + str(e) 
    638669                     
    639670        signatureValue = base64.decodestring(b64EncSignatureValue) 
    640671 
    641  
    642         # Look for X.509 Cert in wsse:BinarySecurityToken node 
     672        # Canonicalize the SignedInfo node and take digest 
     673        signedInfoC14n = Canonicalize(signedInfoNode, **signedInfoC14nKw)         
     674        calcSignedInfoDigestValue = sha(signedInfoC14n).digest() 
     675 
     676        import pdb;pdb.set_trace() 
     677 
     678        # Try extracting X.509 Cert from ds:X509Certificate node in KeyInfo 
    643679        try: 
    644             binSecTokNode = xpath.Evaluate('//wsse:BinarySecurityToken', 
    645                                            contextNode=self.__docNode, 
    646                                            context=ctxt)[0] 
    647             x509str = binSecTokNode.childNodes[0]._get_nodeValue() 
    648             x509strAlt = '' 
    649             i = 0 
    650             while i < len(x509str): 
    651                 x509strAlt += "%s\n" % x509str[i:i+64] 
    652                 i += 64 
     680            x509CertNode = xpath.Evaluate('//ds:X509Certificate', 
     681                                          contextNode=self.__docNode, 
     682                                          context=ctx)[0] 
     683            x509str = x509CertNode.childNodes[0]._get_nodeValue() 
    653684     
    654685            raise Exception, "Try reading from file" 
    655             x509Cert = X509.load_cert_string(x509strAlt) 
     686            x509Cert = X509.load_cert_string(x509str) 
    656687        except: 
    657             # If not, check cert file     
    658             x509Cert = X509.load_cert(self.__certFilePath) 
    659          
     688            # No ds:X509Certificate element present - read from file instead   
     689            x509Cert = X509.load_cert(self.__certFilePathList[0]) 
     690 
     691   
     692        # Once the required information has been extracted from the Signature 
     693        # node it can be removed 
     694        parentNode.removeChild(signatureNode) 
     695         
     696         
     697        # Two stage process: reference validation followed by signature  
     698        # validation  
     699         
     700        # 1) Reference Validation 
     701        # 
     702        # With the Signature node now removed, the parent node can now be 
     703        # canonicalized and the digest calculated 
     704        refC14n = Canonicalize(parentNode, **refC14nKw) 
     705        calcDigestValue = base64.encodestring(sha(refC14n).digest()).strip() 
     706         
     707        # Reference validates if the newly calculated digest value and the  
     708        # digest value extracted from the Reference section of the SignedInfo  
     709        # are the same 
     710        if calcDigestValue != refDigestValue: 
     711            raise InvalidSignature, \ 
     712                            'Digest Values do not match for reference data' 
     713 
     714 
     715        # 2) Signature Validation 
     716        #         
    660717        # Extract RSA public key from the cert 
    661718        rsaPubKey = x509Cert.get_pubkey().get_rsa() 
    662          
    663         # Apply the signature verification 
     719 
     720 
     721        # Compare digest value of the SignedInfo element calculated earlier  
     722        # against the signatureValue read from the SignedInfo 
    664723        try: 
    665             verify = bool(rsaPubKey.verify(signedInfoDigestValue,  
    666                                            signatureValue)) 
     724            verify = rsaPubKey.verify(calcSignedInfoDigestValue,  
     725                                      signatureValue) 
    667726        except RSA.RSAError: 
    668             raise VerifyError, "Error in Signature" 
     727            raise InvalidSignature, "Error in Signature" 
    669728         
    670729        if not verify: 
    671             raise VerifyError, "Invalid signature" 
    672          
    673 #        import pdb;pdb.set_trace() 
     730            raise InvalidSignature, "Invalid signature" 
    674731 
    675732 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/XMLSecDoc/xmlSecDocTest.py

    r1953 r1959  
    7878            getpass.getpass(prompt="\ntest2Sign private key password: ") 
    7979         
    80         self.xmlSecDoc.sign(xmlTxt=self.strXML) 
     80        self.xmlSecDoc.applyEnvelopedSignature(xmlTxt=self.strXML) 
    8181        self.xmlSecDoc.write() 
    8282     
     
    103103        self.xmlSecDoc.certFilePathList=self.cfg['test5Verify']['certfile'] 
    104104        self.xmlSecDoc.read() 
    105         self.xmlSecDoc.verify() 
     105        self.xmlSecDoc.verifyEnvelopedSignature() 
    106106         
    107107  
Note: See TracChangeset for help on using the changeset viewer.