00001
00002
00003
00004
00005
00006
00007 import sys, time, warnings
00008 import sha, base64
00009
00010
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
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
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
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
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
00109 targetNamespace = OASIS.WSSE
00110 sweepInterval = 60*5
00111 nonces = None
00112
00113
00114 PasswordText = targetNamespace + "#PasswordText"
00115 PasswordDigest = targetNamespace + "#PasswordDigest"
00116
00117
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
00147
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
00168 attrs = getattr(password, password.typecode.attrs_aname, {})
00169 pwtype = attrs.get('Type', cls.PasswordText)
00170
00171
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
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
00190
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
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
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
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
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
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