Changeset 1264
- Timestamp:
- 02/13/08 12:33:38 (11 months ago)
- Location:
- trunk/rp/common/python
- Files:
-
- 6 added
- 5 modified
-
doc (added)
-
docs (added)
-
docs/__init__.html (added)
-
docs/event.html (added)
-
docs/infocardlib.html (added)
-
docs/xmlseclibs.html (added)
-
infocard/event.py (modified) (4 diffs)
-
infocard/infocardlib.py (modified) (21 diffs)
-
infocard/xmlseclibs.py (modified) (32 diffs)
-
samples/index.html (modified) (1 diff)
-
samples/process_infocard.psp (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/rp/common/python/infocard/event.py
r1231 r1264 1 # Copyright (c) 2007 Novell, Inc.1 # Copyright (c) 2007, 2008 Novell, Inc. 2 2 # All Rights Reserved. 3 3 … … 8 8 # This library is distributed in the hope that it will be useful, 9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 11 # GNU Lesser General Public License for more details. 12 12 … … 28 28 NOTSET = logging.NOTSET 29 29 30 config_fields = ['CRITICAL', 'DEBUG', 'ERROR', 'FATAL', 'INFO'] 31 32 class IEventLog: 33 """An interface for managing related events, including inline filtering 34 """ 35 36 37 def add_event( eid, text, level): 38 """Add an event to the queue, filtering as needed 39 """ 40 30 config_fields = ['CRITICAL', 'DEBUG', 'ERROR', 'FATAL', 'INFO', 'NOTSET'] 41 31 42 32 class BasicEventLog: 43 """ Implementation of a basic event log.33 """ Implementation of a basic event log. 44 34 45 35 This event log is not intended to be a process level event log, rather it is … … 47 37 This event log allows specific tags to be upgraded or downgraded in severity 48 38 based on configuration 39 """ 40 41 def __init__(self, options=None): 42 """ Process options for basic event log. 43 44 Currently the only options are for overriding severities based on 45 the event tag. To configure a dictionary where the name of the config_fields 46 is the key name and the event tags are the value. Event tags are space seperated. 47 ({'INFO' : 'parse-locate-encrypted signature-missing-InclusiveNameSpaces'}, 48 {'NOTSET' : 'bogus-message'}) 49 50 """ 51 self.events = [] #array of errors, warnings, and other significant events 52 self.options = {} 53 if options: 54 for field in config_fields: 55 if options.has_key(field): 56 self.options[field] = options[field] 57 # self.events.append(Event("setting %s to %s" % (field, options[field]))) 58 59 def _get_severity(self, severity, tag): 60 """ Internal routine to see if the tag has a configured override for it's 61 severity. 62 63 Returns the configured severity if set, otherwise returns the passed 64 severity.""" 65 if tag: 66 for field in config_fields: 67 if self.options.has_key(field) and (re.search(tag, self.options[field]) is not None): 68 mapped_severity = logging.getLevelName(field) 69 # self.events.append(Event("tag %s has severity %s instead of %s" % (tag, field, logging.getLevelName(severity)))) 70 return mapped_severity 71 return severity 72 73 def add_event(self, text, severity=NOTSET, tag=None): 74 """Add an event to the event queue, may upgrade or downgrade the 75 severity, depending on the currently active options.""" 76 77 event = Event(text, self._get_severity(severity, tag), tag) 78 self.events.append(event) 79 80 def has_severity(self, severity): 81 """Get a count of the number of items of equal or greater than the passed 82 severity, with CRITICAL being the largest, and NOTSET being the lowest. 83 84 Useful to check and see if any actual errors occured, in contrast to 85 informational events. 86 """ 87 count = 0 88 for event in self.events: 89 if event.severity >= severity: 90 count += 1 91 # self.events.append(Event("examined %d events and found %d events of %s or greater" % (len(self.events), count, logging.getLevelName(severity)))) 92 return count 93 94 class Event: 95 """Object to hold an indivdual event. 49 96 50 """ 51 config_fields = ['CRITICAL', 'DEBUG', 'ERROR', 'FATAL', 'INFO'] 52 def __init__(self, options=None): 53 """ 54 """ 55 self.events = [] #array of errors, warnings, and other significant events 56 self.options = {} 57 if options: 58 for field in config_fields: 59 if options.has_key(field): 60 self.options[field] = options[field] 61 # self.events.append(Event("setting %s to %s" % (field, options[field]))) 62 63 def _get_severity(self, severity, tag): 64 if tag: 65 for field in config_fields: 66 if self.options.has_key(field) and (re.search(tag, self.options[field]) is not None): 67 mapped_severity = logging.getLevelName(field) 68 # self.events.append(Event("tag %s has severity %s instead of %s" % (tag, field, logging.getLevelName(severity)))) 69 return mapped_severity 70 return severity 71 72 def add_event(self, text, severity=NOTSET, tag=None): 73 """ add an event to the event queue, may upgrade or downgrade the 74 severity, depending on the currently active options.""" 75 76 event = Event(text, self._get_severity(severity, tag), tag) 77 self.events.append(event) 78 79 def has_severity(self, severity): 80 """ used to get a count of the number of items of equal or greater 81 severity with CRITICAL being the largest, and NOTSET being the lowest. 82 Useful to check and see if any actual errors occured, in contrast to 83 informational events. 84 """ 85 count = 0 86 for event in self.events: 87 if event.severity >= severity: 88 count += 1 89 # self.events.append(Event("examined %d events and found %d events of %s or greater" % (len(self.events), count, logging.getLevelName(severity)))) 90 return count 91 92 class Event: 93 """Structure to trace an indivdual event.""" 94 text = None 95 tag = None 96 severity = NOTSET 97 #todo add a datetime 98 99 def __init__(self, text, severity=NOTSET, tag=None): 100 self.text = text 101 self.severity = severity 102 self.tag = tag 103 104 97 Nothing fancy here just a plain old object. 98 99 Todo:: it would be nice to timestamp these 100 """ 101 102 text = None 103 tag = None 104 severity = NOTSET 105 106 def __init__(self, text, severity=NOTSET, tag=None): 107 self.text = text 108 self.severity = severity 109 self.tag = tag 110 111 -
trunk/rp/common/python/infocard/infocardlib.py
r1245 r1264 1 1 # Copyright (c) 2007, 2008 Novell, Inc. 2 2 # All Rights Reserved. 3 # 3 4 4 # This library is free software; you can redistribute it and/or 5 5 # modify it under the terms of the GNU Lesser General Public License as … … 83 83 BEARER_TOKEN = 'urn:oasis:names:tc:SAML:1.0:cm:bearer' 84 84 HOLDER_OF_KEY_TOKEN = 'urn:oasis:names:tc:SAML:1.0:cm:holder-of-key' 85 SENDER_VOUCHES_TOKEN = "urn:oasis:names:tc:SAML:1.0:cm:sender-vouches" 85 86 86 87 """ public identifiers for meta data about the token … … 113 114 114 115 class InfoCardProcessor: 115 116 """Base object for consumers who wish to build a python based RP for 117 processing information cards and dealing with cardspace. 118 119 This object may be created and configured once then used to evaluate many 120 security tokens 121 122 """ 123 116 124 def __init__(self): 117 125 self.options = {} 118 126 119 127 def setDecode(self, privateKey, passPhrase = None, isFile=False, isCert = True): 128 """Setup the cert/private key used to decrypt tokens. In many cases this 129 will be the servers ssl cert. 130 131 always returns None 132 """ 120 133 if isFile: 121 134 fp = open(privateKey, 'rb') … … 130 143 131 144 def setClaims(self, required, optional = None, multivalued = None): 145 """Helper function to simplify setting of the optional and required 146 claims, as well as which claims may be multivalued. 147 148 Failure to tell the processor which claims are expected may result in 149 errors being reported. The process helps verify that all required claims 150 were received and that no additional claims were sent. 151 152 Todo: insert empty claim handling 153 Todo: insert custom claim transformations 154 155 always returns None 156 """ 132 157 self.options[OPTION_required_claims] = required 133 158 self.options[OPTION_optional_claims] = optional … … 135 160 136 161 def setOptions(self, options): 162 """Set options for processing the security token 163 164 The most common options relate to the overriding of event severity, please 165 see evemt.py for details of option name and values. 166 """ 137 167 for name in options.keys(): 138 168 self.options[name] = options[name] … … 145 175 146 176 def processToken(self, xmlToken = None): 147 """Parse the token""" 177 """Parse the token using prevously configured keys, claims and options. 178 179 returns a SecToken objec, the returned secToken may or may not be valid, 180 it is up to the caller to check secToken.isValid() 181 """ 148 182 149 183 secToken = SecToken(self.options) … … 155 189 156 190 class SecToken: 157 """ Class for the parsing and holding of security token data""" 191 """ Class for the parsing and holding of security token data 192 193 Instantiate, configure, process, check validity 194 Typiclly not directly instanciated, instead InfoCardProcessor is used to 195 hold the common configuration and as a factory for creating SecTokens 196 197 Todo: currently only supports SAML 1.0/1.1 tokens, that should be abstracted out 198 to allow for many token types. 199 200 """ 158 201 159 202 def __init__(self, options=None): … … 177 220 178 221 def processToken(self, xmlToken, options): 179 """ process the token, until processed no data is present 222 """Process the token, until processed no data is present 223 224 returns True if valid, False if invalid. Events are logged to 225 self.eventLog detialing failure reasons. 180 226 """ 181 227 … … 185 231 self.eventLog.add_event("Token not supplied or empty", 186 232 event.FATAL, 'empty-token') 187 objenc = xmlseclibs.XMLSecEnc(self.eventLog)188 self.cryptedDom = minidom.parseString(self.crypted)189 encData = objenc.locateEncryptedData(self.cryptedDom)190 if not encData:191 self.eventLog.add_event("Cannot locate encrypted"\192 " data in security token, please ensure all requests"\193 " are sent over SSL.", event.ERROR, 'parse-locate-encrypted')194 self.decrypted = self.cryptedDom195 233 else: 196 objenc.setNode(encData) 197 objenc.type = encData.getAttribute("Type") 198 199 key = None 200 objKey = objenc.locateKey() 201 if (objKey): 202 self.objKeyInfo = objenc.locateKeyInfo(objKey) 203 if (self.objKeyInfo): 204 if (self.objKeyInfo.isEncrypted): 205 objencKey = self.objKeyInfo.encryptedCtx 206 self.objKeyInfo.loadKey( 207 self._option(options, OPTION_CryptoKey), 208 self._option(options, OPTION_CryptoKeyPass), 209 self._option(options, OPTION_CryptoKeyIsFile), 210 self._option(options, OPTION_CryptoKeyIsCert)) 211 key = objencKey.decryptKey(self.objKeyInfo) 212 213 if (not objKey) or (not key): 214 self.eventLog.add_event("Error loading key to handle"\ 215 "Decryption", event.FATAL, 'parse-keyload') 234 objenc = xmlseclibs.XMLSecEnc(self.eventLog) 235 self.cryptedDom = minidom.parseString(self.crypted) 236 encData = objenc.locateEncryptedData(self.cryptedDom) 237 if not encData: 238 self.eventLog.add_event("Cannot locate encrypted"\ 239 " data in security token, please ensure all requests"\ 240 " are sent over SSL.", event.ERROR, 'parse-locate-encrypted') 241 self.decrypted = self.cryptedDom 216 242 else: 217 objKey.loadKey(key) 218 decrypt = objenc.decryptNode(objKey, False) 219 if decrypt: 220 # we have the saml token so load er up 221 self.decrypted = minidom.parseString(decrypt) 243 objenc.setNode(encData) 244 objenc.type = encData.getAttribute("Type") 245 246 key = None 247 objKey = objenc.locateKey() 248 if (objKey): 249 self.objKeyInfo = objenc.locateKeyInfo(objKey) 250 if (self.objKeyInfo): 251 if (self.objKeyInfo.isEncrypted): 252 objencKey = self.objKeyInfo.encryptedCtx 253 self.objKeyInfo.loadKey( 254 self._option(options, OPTION_CryptoKey), 255 self._option(options, OPTION_CryptoKeyPass), 256 self._option(options, OPTION_CryptoKeyIsFile), 257 self._option(options, OPTION_CryptoKeyIsCert)) 258 key = objencKey.decryptKey(self.objKeyInfo) 259 else: 260 self.eventLog.add_event("ObjectKeyInfo is not encrypted", 261 event.INFO, 'parse-object-key-info') 262 263 if (not objKey) or (not key): 264 self.eventLog.add_event("Error loading key to handle"\ 265 "Decryption", event.FATAL, 'parse-keyload') 222 266 else: 223 self.eventLog.add_event('Unable to decrypt token', 224 event.FATAL, 'decrypt') 267 objKey.loadKey(key) 268 decrypt = objenc.decryptNode(objKey, False) 269 if decrypt: 270 # we have the saml token so load er up 271 self.decrypted = minidom.parseString(decrypt) 272 else: 273 self.eventLog.add_event('Unable to decrypt token', 274 event.FATAL, 'decrypt') 225 275 if not self.decrypted: 226 276 self.eventLog.add_event('Unable to parse decrypted token to DOM', … … 244 294 self.eventLog.add_event("Exception encountered while processing Security Token, exception: %s " % (str(errorMsg)), 245 295 event.FATAL, 'exception') 296 return self.isValid 246 297 247 298 def _setupMiscData(self, options): 248 """Setup all of the data about the token we can, not some data like the299 """Setup all of the data about the token we can, note that some data like the 249 300 audience, valid before/after are set in _isValid 301 302 Todo:: currently saml specific 303 304 250 305 """ 251 306 rootNode = self.decrypted.documentElement … … 260 315 261 316 def _isValid(self, options): 262 """ Validate the SAML token 317 """ Validate the token, including signature validation, validity period, 318 osis interop tests, and that the expected claims are present. 319 320 There are some metadata items cached by this function 321 322 Todo:: currently saml specific 263 323 """ 264 324 data = self.decrypted.documentElement.namespaceURI … … 293 353 if (self.objKeyInfo): 294 354 # Handle any additional key processing such as encrypted keys here 295 # self.eventLog.add_event("Potential additional key processing required: %s, %u" \355 # self.eventLog.add_event("Potential additional key processing required: %s, %u" \ 296 356 # % (self.objKeyInfo.type, objKey.isEncrypted), 297 357 # event.INFO) 298 358 pass 359 # else: 360 # self.eventLog.add_event("No Key Info found for %u" \ 361 # % (objKey.isEncrypted), event.INFO) 299 362 retVal = objXMLSecDSig.verify(objKey) 300 363 if not retVal: … … 337 400 338 401 if not self.metadata.has_key(META_Audience): 339 self.eventLog.add_event(" Security Token does not contain"\340 " an Audience restriction.", event.ERROR, 'validate-audience-present')402 self.eventLog.add_event("Your IdP did not include an Audience "\ 403 "restriction in the Security Token.", event.ERROR, 'validate-audience-present') 341 404 342 405 query = '/mysaml:Assertion/mysaml:AttributeStatement/mysaml:Subject/mysaml:SubjectConfirmation/mysaml:ConfirmationMethod' … … 345 408 self.metadata[META_SubjectConfirmation] = node.firstChild.data 346 409 if (not self._doesMatchFuzzy(BEARER_TOKEN, self.metadata[META_SubjectConfirmation], None, None) and 347 not self._doesMatchFuzzy(HOLDER_OF_KEY_TOKEN, self.metadata[META_SubjectConfirmation], None, None)): 348 self.eventLog.add_event("Passed Configuration data \"%s\" doesn't match "\ 410 not self._doesMatchFuzzy(HOLDER_OF_KEY_TOKEN, self.metadata[META_SubjectConfirmation], None, None) 411 and not self._doesMatchFuzzy(SENDER_VOUCHES_TOKEN, self.metadata[META_SubjectConfirmation], None, None)): 412 self.eventLog.add_event("Passed configuration data \"%s\" doesn't match "\ 349 413 "expected configuration data\"%s\"" % 350 (self.metadata[META_SubjectConfirmation], 'holder-of-key or bearer '),414 (self.metadata[META_SubjectConfirmation], 'holder-of-key or bearer or sender vouches'), 351 415 event.ERROR, 'mismatched-confirmation') 352 416 break … … 390 454 391 455 def _doesMatchFuzzy(self, expected, got, clearTag, eventTag): 456 """Lienient matching for items which may be allowed to be missing 457 458 returns true if item is not required or matches. returns False and logs 459 the event on failure. 460 """ 392 461 if not got or not expected or expected == got or re.match(expected, got): 393 462 return True … … 399 468 return False; 400 469 401 #TODO handle namespaces, claim name mapping, multiple values!402 470 def _setupAssertions(self, options): 471 """Build a cache of assertion names/values 472 473 Todo:: handle namespaces, claim name mapping, multiple values, empty 474 value handling and value attributes! 475 476 at compleation self.assertions is setup. Assertions should be accessed 477 by calling getAssertionValues, not by accessing self.assertions. 478 """ 403 479 if not self.assertions or len(self.assertions) == 0: 404 480 mvClaims = [] … … 446 522 def getAssertionValues(self, identifier=None): 447 523 """Allows retrivial of any claim or assertion associated with the security token 524 525 Todo:: visit and finish this function 448 526 Returns either the data or None 449 visit and finish this function!450 527 """ 451 528 #is the identifier a URI or just a short name? … … 460 537 except Exception: 461 538 claim = identifier 462 539 463 540 # self.eventLog.add_event("Looking for ns: \"%s\" claim: \"%s\" " \ 464 541 # % (ns, claim), … … 483 560 return claims 484 561 485 486 562 def getMetaDataValues(self, identifier=None): 487 563 """Allows retrivial of any meta data associated with the security token 564 565 Note: currently all meta data is single valued! 488 566 Pass a specific string for the identifier and receive either a 489 567 string or None 490 568 If the identifier is None then a dictionary of all meta data is returned 491 Currently all meta data is single valued!492 569 """ 493 570 if identifier: … … 500 577 501 578 def _getCardKeyHash(self): 579 """Internal routine to generate a generic cardkey hash, the result is 580 stored in self.metadata[META_CardKeyHash] 581 """ 502 582 try: 503 583 return self.metadata[META_CardKeyHash] … … 517 597 return self.metadata[META_CardKeyHash] 518 598 519 # TODO: need work on ISO date checking - skip for now520 599 def checkDateConditions(start=None, end=None, difference=300): 600 """Validate a datetime is in range. 601 602 difference in seconds allows for clock skew. 603 604 Todo:: need work on ISO date checking - skip for now 605 606 returns True if time is in range False if not in range 607 """ 521 608 currentTime = datetime.datetime.now() 522 609 if (start): 523 610 startTime = datetime.datetime.fromtimestamp(xml.utils.iso8601.parse(start)) 524 # Allow for a 5 minute difference in Time611 # Allow for a difference in clock syncronization, 525 612 d = datetime.timedelta(minutes=difference) 526 613 if ((not startTime) or ((startTime - d) > currentTime)): -
trunk/rp/common/python/infocard/xmlseclibs.py
r1213 r1264 1 # Copyright (c) 2007 Novell, Inc.1 # Copyright (c) 2007,2008 Novell, Inc. 2 2 # All Rights Reserved. 3 # 3 4 4 # This library is free software; you can redistribute it and/or 5 5 # modify it under the terms of the GNU Lesser General Public License as … … 74 74 75 75 def report_event(eventLog, text, severity = event.NOTSET, tag = None): 76 """ module level function for either remembering the event or throwing an 77 exception if eventlonging is not configured. 78 """ 76 79 if eventLog: 77 80 eventLog.add_event(text, severity, tag) … … 81 84 82 85 class XMLSecurityKey: 86 """Class for encrypting/decrypting data, and remembering information about . 87 the methods and keys for doing so. 88 89 Hides from the caller the details of which library (openssl or mcrypt) is 90 used for a particular cipher. 91 92 """ 83 93 TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' 84 94 AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' … … 173 183 174 184 def loadKey(self, key, passPhrase = None, isFile=False, isCert = True): 185 """ setup key information in object 186 187 This function fails if key can't be loaded succesfully 188 Returns None on success and failure, reports errors to the configured event log. 189 """ 175 190 if isFile: 176 191 fp = open(key, 'rb') … … 218 233 return None 219 234 220 #TODO: Convert encryptMcrypt221 235 def encryptMcrypt(self, data): 236 """Encrypted using the mcrypt library 237 238 This function should not be called directly, rather call encryptData and 239 let that function switch based on the configured library. 240 241 Todo:: 242 Not currently called, appears to be incomplete, possibly containing some 243 phpisms 244 """ 222 245 td = mcrypt_module_open(self.cryptParams['cipher'], '', self.cryptParams['mode'], '') 223 self.iv = mcrypt_create_iv (mcrypt_enc_get_iv_size(td), MCRYPT_RAND)246 self.iv = mcrypt_create_iv(mcrypt_enc_get_iv_size(td), MCRYPT_RAND) 224 247 mcrypt_generic_init(td, self.key, self.iv) 225 248 encrypted_data = self.iv.mcrypt_generic(td, data) … … 229 252 230 253 def decryptMcrypt(self, data): 254 """Decrypt using the mcrypt library 255 256 This function should not be called directly, rather call decryptData and 257 let that function switch based on the configured library. 258 259 Todo:: currently only supports MCRYPT_MODE_CBC 260 261 returns None on failure, decrypted data on success. No events logged 262 """ 231 263 dCipher = self.cryptParams['cipher'] 232 264 iv_length = dCipher.block_size …