Changeset 1425 for TI12-security


Ignore:
Timestamp:
21/08/06 12:00:49 (13 years ago)
Author:
pjkersha
Message:

Working sign and verify. Namespace declarations must be the same in the resulting canonicalization otherwise it
won't work. Previously, only the SignedInfo? chunk was read into a dom object from a string. This meant that
other namespaces for the whole document were left out. When verify was called the resulting digest didn't
agree becuase with verify, all the document was read into a dom object and so all namespace declaration had been
included.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/Tests/xmlsec/wsSecurity.py

    r1415 r1425  
    22 
    33import re 
     4 
     5# Digest and signature/verify 
    46from sha import sha 
    57from M2Crypto import X509, BIO, RSA 
    68import base64 
     9 
    710import cElementTree as ElementTree 
    811import ZSI 
    912from ZSI.wstools.Namespaces import DSIG, OASIS, GLOBUS, WSA200403, BEA, SOAP 
    1013 
     14# XML Parsing 
    1115from cStringIO import StringIO 
    1216from Ft.Xml.Domlette import NonvalidatingReaderBase, NonvalidatingReader 
     
    1721from xml.dom import Node 
    1822 
     23# Type codes for signature elements 
     24#from ZSI.TC import _get_global_element_declaration as GED 
     25 
    1926_attrs = lambda E: E._get_attributes() or [] 
    2027_children = lambda E: E._get_childNodes() or [] 
     
    2229 
    2330 
    24 class Signature(object): 
    25     def __init__(self): 
    26         pass 
    27      
    28     def sign(self, pubKeyFilePath, priKeyFilePath, priKeyPwd=None): 
    29  
    30         data = """<Data> 
     31#class BST(str): 
     32#    typecode = GED(OASIS.WSSE, "BinarySecurityToken") 
     33 
     34#KeyInfo = GED(DSIG.BASE, "KeyInfo").pyclass 
     35#Reference = GED(OASIS.WSSE, "Reference").pyclass 
     36#SecurityReference = GED(OASIS.WSSE, "SecurityTokenReference").pyclass 
     37#Signature = GED(DSIG.BASE, "Signature").pyclass 
     38 
     39 
     40class SignatureHandler(object): 
     41     
     42    # Unique token reference 
     43    tokenRef = 10101 
     44     
     45    def __init__(self, sign_body=True, sign_headers=True): 
     46         
     47        self._can_algo = DSIG.C14N 
     48        self._sig_algo = GLOBUS.SIG 
     49        self._dig_algo = DSIG.DIGEST_SHA1 
     50        self._contextID = None 
     51        self._expire_time = 300 
     52        self._setProtocol = False 
     53        self._sign_headers = sign_headers 
     54        self._sign_body = sign_body 
     55 
     56     
     57    def sign2(self, certFilePath, priKeyFilePath, priKeyPwd=None): 
     58        """Sign an XML doc         
     59 
     60        From pyGridWare GssSignatureHandler 
     61 
     62        General algorithm 
     63        - Create the authentication information 
     64        elements (ie BinarySecurityToken) 
     65        and security elements(ie timestamp) 
     66         
     67        - Create digests for anything in the 
     68        body of the Message 
     69 
     70        - Fill in the SignedInfoNode, create elements 
     71        for digests of elements in the header 
     72        leave the content of the element empty. Create 
     73        a SignatureValue element also leavethe content 
     74        of that element empty. 
     75 
     76        - serialize the header 
     77 
     78        - Calculate the digests for elements in the 
     79        header(we had to wait until the header was 
     80        serialized to do this) Put the digests as 
     81        the content for those empty digestValue elements 
     82        that we had created 
     83 
     84        - Calculate the SignatureValue and place it in 
     85        the content of the SignatureValue element 
     86         
     87        """ 
     88 
     89        context_id = self._contextID 
     90 
     91        if self._sign_headers:  
     92            pass#self.signHeaderElements(sw) 
     93             
     94        if self._sign_body:  
     95            pass#self.signBodyElement(sw) 
     96         
     97        pubKey = X509.load_cert(certFilePath) 
     98         
     99        pubKeyPat = re.compile(\ 
     100            '-----BEGIN CERTIFICATE-----\n?(.*?)\n?-----END CERTIFICATE-----', 
     101            re.S) 
     102        x509Cert = pubKeyPat.findall(pubKey.as_pem())[0] 
     103 
     104        import pdb;pdb.set_trace() 
     105        binarySecurityToken = BST(x509Cert) 
     106        #move this GssSecureMessage 
     107        tokenRefString = self.getTokenRefString() 
     108        binarySecurityToken._attrs = \ 
     109        { 
     110            (OASIS.UTILITY,'Id'):  "CertId-" + tokenRefString, 
     111            'ValueType':           "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1", 
     112            'EncodingType':        "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" 
     113        } 
     114        attrTypeCodeDict = \ 
     115        { 
     116            (OASIS.UTILITY, 'Id'):  ZSI.TC.String(), 
     117            'ValueType':            ZSI.TC.String(), 
     118            'EncodingType':         ZSI.TC.String() 
     119        } 
     120        binarySecurityToken.typecode.attribute_typecode_dict.update(attrTypeCodeDict) 
     121 
     122          
     123        typecode = GED(NS1, "Security") 
     124        pyobj = typecode.pyclass() 
     125        pyobj._any = [] 
     126        pyobj._any.append(binarySecurityToken)    
     127              
     128                                  
     129        sig = Signature() 
     130        pyobj._any.append(sig) 
     131        sig._SignedInfo = Holder() 
     132        sig._SignedInfo._CanonicalizationMethod = Holder() 
     133        sig._SignedInfo._CanonicalizationMethod._attrs = {'Algorithm':self._can_algo} 
     134        sig._SignedInfo._SignatureMethod = Holder() 
     135        sig._SignedInfo._SignatureMethod._attrs = {'Algorithm':self._sig_algo} 
     136        sig._SignedInfo._Reference = [] 
     137        sig._SignatureValue = '' 
     138         
     139        sig._KeyInfo = KeyInfo() 
     140        secRef = SecurityReference() 
     141        secRef._any = Reference() 
     142        secRef._any._attrs = {} 
     143        secRef._any._attrs['URI'] = "#CertId-" + tokenRefString 
     144        sig._KeyInfo._any = secRef  
     145 
     146        ''' 
     147        processorNss={'wsu':OASIS.UTILITY, 'ds':DSIG.BASE} 
     148        sw.dom.setContext(processorNss=processorNss) 
     149        nodes = sw.dom.evaluate(expression='//*[@wsu:Id]') 
     150 
     151        #Calculate the digests in the body 
     152        for node in nodes: 
     153            URI = '#%s' %node.getAttributeValue(OASIS.UTILITY, 'Id') 
     154            ref = Holder() 
     155            ref._attrs = {'URI': URI} 
     156            ref._DigestMethod = Holder() 
     157            ref._DigestMethod._attrs = {'Algorithm':self._dig_algo} 
     158            ref._DigestValue = self.getDigestValueFromSoapWriter(sw, URI) 
     159            sig._SignedInfo._Reference.append(ref) 
     160 
     161        #These haven't been serialized yet, the id's to take 
     162        #digests of in the security header 
     163        idUriList = [] 
     164        for idUri in idUriList: 
     165            ref = Holder() 
     166            ref._attrs = {'URI': idUri} 
     167            ref._DigestMethod = Holder() 
     168            ref._DigestMethod._attrs = {'Algorithm':self._dig_algo} 
     169            ref._DigestValue = '' 
     170            sig._SignedInfo._Reference.append(ref) 
     171 
     172        sw.serialize_header(pyobj, typecode) 
     173 
     174        # Reset Context after append on DOM 
     175        sw.dom.setContext(processorNss=processorNss) 
     176        for URI in idUriList: 
     177            digestValue = self.getDigestValueFromSoapWriter(sw, URI) 
     178            referenceNodes = sw.dom.evaluate(expression='//ds:Reference') 
     179             
     180            for referenceNode in referenceNodes: 
     181                refUri = referenceNode.getAttributeValue(None, "URI") 
     182                if refUri == URI: 
     183                    digestValueNode = referenceNode.getElement(DSIG.BASE, "DigestValue") 
     184                    digestValueNode.createAppendTextNode(base64.encodestring(digestValue)) 
     185             
     186        # Reset Context after append on DOM 
     187        sw.dom.setContext(processorNss=processorNss) 
     188        nodes = sw.dom.evaluate(expression='//ds:SignedInfo') 
     189        signedInfo = nodes[0] 
     190        signedInfoStr = signedInfo.canonicalize() 
     191        nodes = sw.dom.evaluate(expression='//ds:SignatureValue') 
     192        signatureValueNode = nodes[0] 
     193        if useSecureMessage: 
     194            hashedSignedInfoStr = sha.sha(signedInfoStr).digest() 
     195            signatureValue = base64.encodestring(auth.sign(hashedSignedInfoStr)) 
     196        else: 
     197            signatureValue = base64.encodestring(secContext.getMIC(signedInfoStr)) 
     198        signatureValueNode.createAppendTextNode(signatureValue) 
     199''' 
     200 
     201    def sign(self, certFilePath, priKeyFilePath, priKeyPwd=None): 
     202 
     203        msgTmpl = """<?xml version="1.0" encoding="UTF-8"?> 
     204<!-- 
     205SOAP Message with WSSE Signature 
     206--> 
     207<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
     208    <soapenv:Header> 
     209        <wsse:Security xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/04/secext" xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/04/utility" soapenv:mustUnderstand="1"> 
     210            <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> 
     211                <ds:SignedInfo> 
     212                    <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> 
     213                    <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> 
     214                    <ds:Reference URI="./wsseSign-doc.xml"> 
     215                        <ds:Transforms> 
     216                            <ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> 
     217                        </ds:Transforms> 
     218                        <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> 
     219                        <ds:DigestValue>%s</ds:DigestValue> 
     220                    </ds:Reference> 
     221                </ds:SignedInfo> 
     222                <ds:SignatureValue>%s</ds:SignatureValue> 
     223                <ds:KeyInfo> 
     224                    <ds:X509Data> 
     225                        <ds:X509Certificate> 
     226%s 
     227                        </ds:X509Certificate> 
     228                    </ds:X509Data> 
     229                </ds:KeyInfo> 
     230            </ds:Signature> 
     231        </wsse:Security> 
     232    </soapenv:Header> 
     233    <soapenv:Body xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/04/utility" wsu:Id="msgBody"> 
    31234    Hello, World! 
    32     </Data>""" 
    33  
    34         tmpl = """<?xml version="1.0" encoding="UTF-8"?> 
    35 <!--  
    36 XML Security Library example: Original XML doc file for sign3 example.  
    37 --> 
    38 <Envelope xmlns="urn:envelope"> 
    39     %s%s 
    40 </Envelope>""" 
    41  
    42         signatureTmpl = """ 
    43     <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> 
    44         %s 
    45         <SignatureValue>%s</SignatureValue> 
    46         <KeyInfo> 
    47             <X509Data> 
    48                 <X509Certificate>%s</X509Certificate> 
    49             </X509Data> 
    50         </KeyInfo> 
    51     </Signature>""" 
    52      
    53         signedInfoTmpl = '''<SignedInfo> 
    54             <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> 
    55             <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> 
    56             <Reference URI="./wsseSign-doc.xml"> 
    57                 <Transforms> 
    58                     <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> 
    59                 </Transforms> 
    60                 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> 
    61                 <DigestValue>%s</DigestValue> 
    62             </Reference> 
    63         </SignedInfo>''' 
    64  
    65 #        import pdb;pdb.set_trace() 
    66         from xml.dom.ext.reader import PyExpat 
    67         reader = PyExpat.Reader() 
    68 #        dom = reader.fromString(tmpl % (data, '')) 
    69         dom = reader.fromString(open('./wsseSign-doc.xml').read()) 
    70         c14nData = Canonicalize(dom) 
    71          
     235    </soapenv:Body> 
     236</soapenv:Envelope>""" 
     237 
     238 
     239        # 1) Reference Generation 
     240        # 
     241        # Reference - hard code as local file for now 
     242        refURIstream = open("./wsseSign-doc.xml") 
     243 
     244        # Canonicalize reference 
     245        refDom = NonvalidatingReader.parseStream(refURIstream) 
     246        c14nRef = Canonicalize(refDom) 
     247         
     248        # Calculate digest for reference and base 64 encode 
     249        # 
    72250        # Nb. encodestring adds a trailing newline char 
    73         digestValue = base64.encodestring(sha(c14nData).digest()).strip() 
    74          
    75         signedInfoTxt = signedInfoTmpl % digestValue 
    76          
    77         # Read Private key in order to signature        
    78         priKeyFile = BIO.File(open(priKeyFilePath))                                             
    79         priKey = RSA.load_key_bio(priKeyFile,  
    80                                   callback=lambda *args, **kwargs: priKeyPwd) 
    81  
    82 #        signedInfoTmp = '''<SignedInfo> 
    83 #<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> 
    84 #<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> 
    85 #<Reference URI="./wsseSign-doc.xml"> 
    86 #<Transforms> 
    87 #<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> 
    88 #</Transforms> 
    89 #<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> 
    90 #<DigestValue>2pbmQrrMCFidYKBDMEEVm3ze5H8=</DigestValue> 
    91 #</Reference> 
    92 #</SignedInfo>''' 
     251        digestValue = base64.encodestring(sha(c14nRef).digest()).strip() 
     252         
     253         
     254        # 2) Signature Generation 
     255        # 
     256        # Add digest into SignedInfo 
     257        msgTxt = msgTmpl % (digestValue, '%s', '%s') 
    93258 
    94259        import pdb;pdb.set_trace() 
    95260 
    96         xmlTxt = open('./wsseSign-xmlsec-res.xml').read() 
    97         dom = NonvalidatingReader.parseStream(StringIO(xmlTxt)) 
    98          
     261        # Test against signature generated by pyXMLSec version 
     262        #xmlTxt = open('./wsseSign-xmlsec-res.xml').read() 
     263        #dom = NonvalidatingReader.parseStream(StringIO(xmlTxt)) 
     264         
     265        # Parse SignedInfo into DOM object ready for canonicalization function 
     266        dom = NonvalidatingReader.parseStream(StringIO(msgTxt)) 
     267         
     268        # Namespaces 
    99269        processorNss = \ 
    100270        { 
     
    108278        } 
    109279        context = XPath.Context.Context(dom, processorNss=processorNss) 
    110                  
     280          
     281        # Get the SignedInfo node and canonicalize  
     282        # 
     283        # Nb. When extracted the code adds the namespace attrinute to the 
     284        # signedInfo!  This has important consequences for validation - 
     285        # 
     286        # 1) Do you strip the namespace attribute before taking the digest to  
     287        # ensure the text is exactly the same as what is displayed in the  
     288        # message? 
     289        # 
     290        # 2) Leave it in and assume the validation algorithm will expect to  
     291        # add in the namespace attribute?! 
     292        # 
     293        # http://www.w3.org/TR/xml-c14n#NoNSPrefixRewriting implies you need  
     294        # to include namespace declarations for namespaces referenced in a doc 
     295        # subset - yes to 2) 
    111296        signedInfoNode = XPath.Compile('//ds:SignedInfo').evaluate(context)[0] 
    112                 
     297        #signedInfoNode = dom.childNodes[0] 
    113298        c14nSignedInfo = Canonicalize(signedInfoNode) 
    114          
     299 
     300        # Calculate digest of SignedInfo 
    115301        signedInfoDigestValue = sha(c14nSignedInfo).digest().strip() 
    116         signatureValue = base64.encodestring(priKey.sign(signedInfoDigestValue)) 
    117  
    118  
    119         pubKey = X509.load_cert(pubKeyFilePath) 
    120          
    121         pubKeyPat = re.compile(\ 
     302         
     303        # Read Private key to sign with     
     304        priKeyFile = BIO.File(open(priKeyFilePath))                                             
     305        priKey = RSA.load_key_bio(priKeyFile,  
     306                                  callback=lambda *args, **kwargs: priKeyPwd) 
     307         
     308        # Sign using the private key and base 64 encode the result 
     309        signatureValue = priKey.sign(signedInfoDigestValue) 
     310        b64EncSignatureValue = base64.encodestring(signatureValue).strip() 
     311 
     312        # Get X.509 cert for inclusion into KeyInfo 
     313        x509Cert = X509.load_cert(certFilePath) 
     314         
     315        x509CertPat = re.compile(\ 
    122316            '-----BEGIN CERTIFICATE-----\n?(.*?)\n?-----END CERTIFICATE-----', 
    123317            re.S) 
    124         x509Cert = pubKeyPat.findall(pubKey.as_pem())[0] 
    125          
    126         signatureXML = signatureTmpl % (signedInfoTxt, signatureValue, x509Cert) 
    127          
    128         return tmpl % (data, signatureXML) 
    129      
    130          
    131     __call__ = sign 
    132  
    133  
    134     def verify(self, xmlTxt): 
     318        x509CertStr = x509CertPat.findall(x509Cert.as_pem())[0] 
     319         
     320        signedMsgTxt = msgTxt % (b64EncSignatureValue, x509CertStr) 
     321         
     322        # Extract RSA public key from the cert 
     323        rsaPubKey = x509Cert.get_pubkey().get_rsa() 
     324         
     325        # Check the signature  
     326        verify = bool(rsaPubKey.verify(signedInfoDigestValue, signatureValue)) 
     327         
     328        return signedMsgTxt 
     329 
     330 
     331    def verify(self, xmlTxt, certFilePath=None): 
    135332        """Verify signature""" 
    136333        import pdb;pdb.set_trace() 
     
    209406                                     "CanonicalizationMethod")[0] 
    210407                                              
    211         algorithm = c14nNode.getAttributeNodeNS(None, 'Algorithm').value 
     408        algorithm = c14nMethodNode.getAttributeNodeNS(None, 'Algorithm').value 
    212409 
    213410        # Canonicalize the SignedInfo node and take digest 
     
    221418 
    222419        # Remove base 64 encoding 
    223         b64EncSignedInfoDigestValue = \ 
     420        b64EncSignatureValue = \ 
    224421                    str(signatureValueNode.childNodes[0].nodeValue).strip() 
    225422                     
    226         nodeSignedInfoDigestValue = \ 
    227                     base64.decodestring(b64EncSignedInfoDigestValue) 
     423        signatureValue = base64.decodestring(b64EncSignatureValue) 
    228424 
    229425 
    230426        # Read X.509 Cert from wsse:BinarySecurityToken node 
    231427        # - leave out for now and read direct from hard coded pem file 
    232         x509Cert = X509.load_cert('../Junk-cert.pem') 
     428        x509Cert = X509.load_cert(certFilePath) 
    233429         
    234430        # Extract RSA public key from the cert 
     
    236432         
    237433        # Apply the signature verification 
    238         verify = rsaPubKey.verify(signedInfoDigestValue,  
    239                                   nodeSignedInfoDigestValue) 
    240          
    241         return bool(verify) 
     434        try: 
     435            verify = bool(rsaPubKey.verify(signedInfoDigestValue,  
     436                                           signatureValue)) 
     437        except RSA.RSAError: 
     438            verify = False 
     439             
     440        return verify 
     441 
     442 
     443    def getTokenRefString(self): 
     444        """ 
     445        Return an unique number to identify an element 
     446        for the element's id attribute 
     447        """ 
     448        Signature.tokenRef +=1 
     449        return str(Signature.tokenRef) 
    242450 
    243451     
    244452       
    245453if __name__ == "__main__": 
    246     s = Signature() 
    247 #    txt = s.sign('../Junk-cert.pem', 
    248 #           '../Junk-key.pem',  
    249 #           open('../tmp2').read().strip())       
    250 #    print txt  
    251     s.verify(open('./wsseSign-xmlsec-res.xml').read()) 
     454    import sys 
     455    txt = None 
     456     
     457    s = SignatureHandler() 
     458     
     459    if 'sign' in sys.argv: 
     460        txt = s.sign('../Junk-cert.pem', 
     461                     '../Junk-key.pem',  
     462                     open('../tmp2').read().strip())       
     463        print txt 
     464 
     465    if 'verify' in sys.argv: 
     466        if txt is None: 
     467            txt = open('./wsseSign-test-res.xml').read() 
     468             
     469        print "Signature OK? %s" % \ 
     470                            s.verify(txt, certFilePath='../Junk-cert.pem') 
Note: See TracChangeset for help on using the changeset viewer.