• Main Page
  • Related Pages
  • Modules
  • Namespaces
  • Data Structures
  • Files
  • File List
  • Globals

contrib/opal/ZSI/ZSI/twisted/WSsecurity.py

00001 ###########################################################################
00002 # Joshua R. Boverhof, LBNL
00003 # See Copyright for copyright notice!
00004 # $Id: WSsecurity.py 1134 2006-02-24 00:23:06Z boverhof $
00005 ###########################################################################
00006 
00007 import sys, time, warnings
00008 import sha, base64
00009 
00010 # twisted & related imports
00011 from zope.interface import classProvides, implements, Interface
00012 from twisted.python import log, failure
00013 from twisted.web.error import NoResource
00014 from twisted.web.server import NOT_DONE_YET
00015 from twisted.internet import reactor
00016 import twisted.web.http
00017 import twisted.web.resource
00018 
00019 # ZSI imports
00020 from ZSI import _get_element_nsuri_name, EvaluateException, ParseException
00021 from ZSI.parse import ParsedSoap
00022 from ZSI.writer import SoapWriter
00023 from ZSI.TC import _get_global_element_declaration as GED
00024 from ZSI import fault
00025 from ZSI.wstools.Namespaces import OASIS, DSIG
00026 from WSresource import DefaultHandlerChain, HandlerChainInterface,\
00027     WSAddressCallbackHandler, DataHandler, WSAddressHandler
00028 
00029 
00030 #
00031 # Global Element Declarations
00032 # 
00033 UsernameTokenDec = GED(OASIS.WSSE, "UsernameToken")
00034 SecurityDec = GED(OASIS.WSSE, "Security")
00035 SignatureDec = GED(DSIG.BASE, "Signature")
00036 PasswordDec = GED(OASIS.WSSE, "Password")
00037 NonceDec = GED(OASIS.WSSE, "Nonce")
00038 CreatedDec = GED(OASIS.UTILITY, "Created")
00039 
00040 if None in [UsernameTokenDec,SecurityDec,SignatureDec,PasswordDec,NonceDec,CreatedDec]:
00041     raise ImportError, 'required global element(s) unavailable: %s ' %({
00042         (OASIS.WSSE, "UsernameToken"):UsernameTokenDec,
00043         (OASIS.WSSE, "Security"):SecurityDec,
00044         (DSIG.BASE, "Signature"):SignatureDec,
00045         (OASIS.WSSE, "Password"):PasswordDec,
00046         (OASIS.WSSE, "Nonce"):NonceDec,
00047         (OASIS.UTILITY, "Created"):CreatedDec,
00048         })
00049     
00050     
00051 # 
00052 # Stability: Unstable, Untested, Not Finished.
00053 # 
00054 
00055 class WSSecurityHandler:
00056     """Web Services Security: SOAP Message Security 1.0
00057     
00058     Class Variables:
00059         debug -- If True provide more detailed SOAP:Fault information to clients.
00060     """
00061     classProvides(HandlerChainInterface)
00062     debug = True
00063     
00064     @classmethod
00065     def processRequest(cls, ps, **kw):
00066         if type(ps) is not ParsedSoap:
00067             raise TypeError,'Expecting ParsedSoap instance'
00068         
00069         security = ps.ParseHeaderElements([cls.securityDec])
00070         
00071         # Assume all security headers are supposed to be processed here.
00072         for pyobj in security or []:
00073             for any in pyobj.Any or []:
00074                 
00075                 if any.typecode is UsernameTokenDec:
00076                     try:
00077                         ps = cls.UsernameTokenProfileHandler.processRequest(ps, any)
00078                     except Exception, ex:
00079                         if cls.debug: raise
00080                         raise RuntimeError, 'Unauthorized Username/passphrase combination'
00081                     continue
00082                 
00083                 if any.typecode is SignatureDec:
00084                     try:
00085                         ps = cls.SignatureHandler.processRequest(ps, any)
00086                     except Exception, ex:
00087                         if cls.debug: raise
00088                         raise RuntimeError, 'Invalid Security Header'
00089                     continue
00090                 
00091                 raise RuntimeError, 'WS-Security, Unsupported token %s' %str(any)
00092             
00093         return ps
00094 
00095     @classmethod
00096     def processResponse(cls, output, **kw):
00097         return output
00098 
00099 
00100     class UsernameTokenProfileHandler:
00101         """Web Services Security UsernameToken Profile 1.0
00102         
00103         Class Variables:
00104             targetNamespace --
00105         """
00106         classProvides(HandlerChainInterface)
00107         
00108         # Class Variables
00109         targetNamespace = OASIS.WSSE
00110         sweepInterval = 60*5
00111         nonces = None
00112             
00113         # Set to None to disable
00114         PasswordText = targetNamespace + "#PasswordText"
00115         PasswordDigest = targetNamespace + "#PasswordDigest"
00116             
00117         # Override passwordCallback 
00118         passwordCallback = lambda cls,username: None
00119         
00120         @classmethod
00121         def sweep(cls, index):
00122             """remove nonces every sweepInterval.
00123             Parameters:
00124                 index -- remove all nonces up to this index.
00125             """
00126             if cls.nonces is None: 
00127                 cls.nonces = []
00128             
00129             seconds = cls.sweepInterval
00130             cls.nonces = cls.nonces[index:]
00131             reactor.callLater(seconds, cls.sweep, len(cls.nonces))
00132         
00133         @classmethod
00134         def processRequest(cls, ps, token, **kw):
00135             """
00136             Parameters:
00137                 ps -- ParsedSoap instance
00138                 token -- UsernameToken pyclass instance
00139             """
00140             if token.typecode is not UsernameTokenDec:
00141                 raise TypeError, 'expecting GED (%s,%s) representation.' %(
00142                     UsernameTokenDec.nspname, UsernameTokenDec.pname)
00143                     
00144             username = token.Username
00145             
00146             # expecting only one password
00147             # may have a nonce and a created
00148             password = nonce = timestamp = None
00149             for any in token.Any or []:
00150                 if any.typecode is PasswordDec:
00151                     password = any
00152                     continue
00153                 
00154                 if any.typecode is NonceTypeDec:
00155                     nonce = any
00156                     continue
00157                 
00158                 if any.typecode is CreatedTypeDec:
00159                     timestamp = any
00160                     continue
00161                 
00162                 raise TypeError, 'UsernameTokenProfileHander unexpected %s' %str(any)
00163 
00164             if password is None:
00165                 raise RuntimeError, 'Unauthorized, no password'
00166             
00167             # TODO: not yet supporting complexType simpleContent in pyclass_type
00168             attrs = getattr(password, password.typecode.attrs_aname, {})
00169             pwtype = attrs.get('Type', cls.PasswordText)
00170             
00171             # Clear Text Passwords
00172             if cls.PasswordText is not None and pwtype == cls.PasswordText:
00173                 if password == cls.passwordCallback(username):
00174                     return ps
00175                 
00176                 raise RuntimeError, 'Unauthorized, clear text password failed'
00177             
00178             if cls.nonces is None: cls.sweep(0)
00179             if nonce is not None:
00180                 if nonce in cls.nonces:
00181                     raise RuntimeError, 'Invalid Nonce'
00182                 
00183                 # created was 10 seconds ago or sooner
00184                 if created is not None and created < time.gmtime(time.time()-10):
00185                     raise RuntimeError, 'UsernameToken created is expired' 
00186                 
00187                 cls.nonces.append(nonce)
00188             
00189             # PasswordDigest, recommended that implemenations
00190             # require a Nonce and Created
00191             if cls.PasswordDigest is not None and pwtype == cls.PasswordDigest:
00192                 digest = sha.sha()
00193                 for i in (nonce, created, cls.passwordCallback(username)):
00194                     if i is None: continue
00195                     digest.update(i)
00196 
00197                 if password == base64.encodestring(digest.digest()).strip():
00198                     return ps
00199                 
00200                 raise RuntimeError, 'Unauthorized, digest failed'
00201             
00202             raise RuntimeError, 'Unauthorized, contents of UsernameToken unknown'
00203             
00204         @classmethod
00205         def processResponse(cls, output, **kw):
00206             return output
00207         
00208     @staticmethod
00209     def hmac_sha1(xml):
00210         return 
00211     
00212     class SignatureHandler:
00213         """Web Services Security UsernameToken Profile 1.0
00214         """
00215         digestMethods = {
00216             DSIG.BASE+"#sha1":sha.sha,
00217             }
00218         signingMethods = {
00219             DSIG.BASE+"#hmac-sha1":hmac_sha1,
00220             }
00221         canonicalizationMethods = {
00222             DSIG.C14N_EXCL:lambda node: Canonicalize(node, unsuppressedPrefixes=[]),
00223             DSIG.C14N:lambda node: Canonicalize(node),
00224             }
00225             
00226         @classmethod
00227         def processRequest(cls, ps, signature, **kw):
00228             """
00229             Parameters:
00230                 ps -- ParsedSoap instance
00231                 signature -- Signature pyclass instance
00232             """
00233             if token.typecode is not SignatureDec:
00234                 raise TypeError, 'expecting GED (%s,%s) representation.' %(
00235                     SignatureDec.nspname, SignatureDec.pname)
00236                     
00237             si = signature.SignedInfo
00238             si.CanonicalizationMethod
00239             calgo = si.CanonicalizationMethod.get_attribute_Algorithm()
00240             for any in si.CanonicalizationMethod.Any:
00241                 pass
00242             
00243             # Check Digest
00244             si.Reference
00245             context = XPath.Context.Context(ps.dom, processContents={'wsu':OASIS.UTILITY})
00246             exp = XPath.Compile('//*[@wsu:Id="%s"]' %si.Reference.get_attribute_URI())
00247             nodes = exp.evaluate(context)
00248             if len(nodes) != 1:
00249                 raise RuntimeError, 'A SignedInfo Reference must refer to one node %s.' %(
00250                     si.Reference.get_attribute_URI())
00251                     
00252             try:
00253                 xml = cls.canonicalizeMethods[calgo](nodes[0])
00254             except IndexError:
00255                 raise RuntimeError, 'Unsupported canonicalization algorithm'
00256             
00257             try:
00258                 digest = cls.digestMethods[salgo]
00259             except IndexError:
00260                 raise RuntimeError, 'unknown digestMethods Algorithm'
00261             
00262             digestValue = base64.encodestring(digest(xml).digest()).strip()
00263             if si.Reference.DigestValue != digestValue:
00264                 raise RuntimeError, 'digest does not match'
00265             
00266             if si.Reference.Transforms:
00267                 pass
00268             
00269             signature.KeyInfo
00270             signature.KeyInfo.KeyName
00271             signature.KeyInfo.KeyValue
00272             signature.KeyInfo.RetrievalMethod
00273             signature.KeyInfo.X509Data
00274             signature.KeyInfo.PGPData
00275             signature.KeyInfo.SPKIData
00276             signature.KeyInfo.MgmtData
00277             signature.KeyInfo.Any 
00278             
00279             signature.Object
00280             
00281             # TODO: Check Signature
00282             signature.SignatureValue
00283             si.SignatureMethod
00284             salgo = si.SignatureMethod.get_attribute_Algorithm()
00285             if si.SignatureMethod.HMACOutputLength:
00286                 pass
00287             for any in si.SignatureMethod.Any:
00288                 pass
00289             
00290             # <SignedInfo><Reference URI="">
00291             exp = XPath.Compile('//child::*[attribute::URI = "%s"]/..' %(
00292                                  si.Reference.get_attribute_URI()))
00293             nodes = exp.evaluate(context)
00294             if len(nodes) != 1:
00295                 raise RuntimeError, 'A SignedInfo Reference must refer to one node %s.' %(
00296                     si.Reference.get_attribute_URI())
00297                     
00298             try:
00299                 xml = cls.canonicalizeMethods[calgo](nodes[0])
00300             except IndexError:
00301                 raise RuntimeError, 'Unsupported canonicalization algorithm'
00302             
00303             # TODO: Check SignatureValue
00304             
00305         @classmethod
00306         def processResponse(cls, output, **kw):
00307             return output
00308         
00309 
00310     class X509TokenProfileHandler:
00311         """Web Services Security UsernameToken Profile 1.0
00312         """
00313         targetNamespace = DSIG.BASE
00314         
00315         # Token Types
00316         singleCertificate = targetNamespace + "#X509v3"
00317         certificatePath = targetNamespace + "#X509PKIPathv1"
00318         setCerticatesCRLs = targetNamespace + "#PKCS7"
00319         
00320         @classmethod
00321         def processRequest(cls, ps, signature, **kw):
00322             return ps
00323 
00324 
00325 
00326 """
00327 <element name="KeyInfo" type="ds:KeyInfoType"/>
00328 <complexType name="KeyInfoType" mixed="true">
00329   <choice maxOccurs="unbounded">
00330     <element ref="ds:KeyName"/>
00331     <element ref="ds:KeyValue"/>
00332     <element ref="ds:RetrievalMethod"/>
00333     <element ref="ds:X509Data"/>
00334     <element ref="ds:PGPData"/>
00335     <element ref="ds:SPKIData"/>
00336     <element ref="ds:MgmtData"/>
00337     <any processContents="lax" namespace="##other"/>
00338     <!-- (1,1) elements from (0,unbounded) namespaces -->
00339   </choice>
00340   <attribute name="Id" type="ID" use="optional"/>
00341 </complexType>
00342 
00343 
00344 
00345 <element name="Signature" type="ds:SignatureType"/>
00346 <complexType name="SignatureType">
00347   <sequence>
00348     <element ref="ds:SignedInfo"/>
00349     <element ref="ds:SignatureValue"/>
00350     <element ref="ds:KeyInfo" minOccurs="0"/>
00351     <element ref="ds:Object" minOccurs="0" maxOccurs="unbounded"/>
00352   </sequence>
00353   <attribute name="Id" type="ID" use="optional"/>
00354 </complexType>
00355 
00356   <element name="SignatureValue" type="ds:SignatureValueType"/>
00357   <complexType name="SignatureValueType">
00358     <simpleContent>
00359       <extension base="base64Binary">
00360         <attribute name="Id" type="ID" use="optional"/>
00361       </extension>
00362     </simpleContent>
00363   </complexType>
00364 
00365 <!-- Start SignedInfo -->
00366 
00367 <element name="SignedInfo" type="ds:SignedInfoType"/>
00368 <complexType name="SignedInfoType">
00369   <sequence>
00370     <element ref="ds:CanonicalizationMethod"/>
00371     <element ref="ds:SignatureMethod"/>
00372     <element ref="ds:Reference" maxOccurs="unbounded"/>
00373   </sequence> 
00374   <attribute name="Id" type="ID" use="optional"/>
00375 </complexType>
00376 """
00377 
00378 
00379 class WSSecurityHandlerChainFactory:
00380     protocol = DefaultHandlerChain
00381     
00382     @classmethod
00383     def newInstance(cls):
00384             
00385         return cls.protocol(WSAddressCallbackHandler, DataHandler, 
00386             WSSecurityHandler, WSAddressHandler())
00387 
00388 
00389 

Generated on Wed Oct 20 2010 11:12:16 for APBS by  doxygen 1.7.2