# # Copyright 2008 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Utility functions for keyczar package. @author: arkajit.dey@gmail.com (Arkajit Dey) """ import base64 import cPickle import codecs import functools import math import os import struct import warnings import sys try: # Import hashlib if Python >= 2.5 from hashlib import sha1 except ImportError: from sha import sha as sha1 try: # check for presence of blocking I/O module from io import BlockingIOError except ImportError: class BlockingIOError(IOError): """Exception raised when I/O would block on a non-blocking I/O stream.""" def __init__(self, errno, strerror, characters_written=0): super(IOError, self).__init__(errno, strerror) if not isinstance(characters_written, (int, long)): raise TypeError("characters_written must be a integer") self.characters_written = characters_written from pyasn1.codec.der import decoder from pyasn1.codec.der import encoder from pyasn1.type import univ import errors try: from abc import ABCMeta, abstractmethod, abstractproperty except ImportError: # to keep compatible with older Python versions. class ABCMeta(type): pass def abstractmethod(funcobj): return funcobj def abstractproperty(funcobj): return property(funcobj) HLEN = sha1().digest_size # length of the hash output #RSAPrivateKey ::= SEQUENCE { # version Version, # modulus INTEGER, -- n # publicExponent INTEGER, -- e # privateExponent INTEGER, -- d # prime1 INTEGER, -- p # prime2 INTEGER, -- q # exponent1 INTEGER, -- d mod (p-1) # exponent2 INTEGER, -- d mod (q-1) # coefficient INTEGER -- (inverse of q) mod p } # #Version ::= INTEGER RSA_OID = univ.ObjectIdentifier('1.2.840.113549.1.1.1') RSA_PARAMS = ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'invq'] DSA_OID = univ.ObjectIdentifier('1.2.840.10040.4.1') DSA_PARAMS = ['p', 'q', 'g'] # only algorithm params, not public/private keys SHA1RSA_OID = univ.ObjectIdentifier('1.2.840.113549.1.1.5') SHA1_OID = univ.ObjectIdentifier('1.3.14.3.2.26') # the standard buffer size for streaming DEFAULT_STREAM_BUFF_SIZE = 4096 # environment variable that holds a list of additional plugin backend paths BACKEND_PATHS_ENV_VAR = 'KEYCZAR_BACKEND_PATHS' def ASN1Sequence(*vals): seq = univ.Sequence() for i in range(len(vals)): seq.setComponentByPosition(i, vals[i]) return seq def ParseASN1Sequence(seq): return [seq.getComponentByPosition(i) for i in range(len(seq))] #PrivateKeyInfo ::= SEQUENCE { # version Version, # # privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, # privateKey PrivateKey, # attributes [0] IMPLICIT Attributes OPTIONAL } # #Version ::= INTEGER # #PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier # #PrivateKey ::= OCTET STRING # #Attributes ::= SET OF Attribute def ParsePkcs8(pkcs8): seq = ParseASN1Sequence(decoder.decode(Base64WSDecode(pkcs8))[0]) if len(seq) != 3: # need three fields in PrivateKeyInfo raise errors.KeyczarError("Illegal PKCS8 String.") version = int(seq[0]) if version != 0: raise errors.KeyczarError("Unrecognized PKCS8 Version") [oid, alg_params] = ParseASN1Sequence(seq[1]) key = decoder.decode(seq[2])[0] # Component 2 is an OCTET STRING which is further decoded params = {} if oid == RSA_OID: key = ParseASN1Sequence(key) version = int(key[0]) if version != 0: raise errors.KeyczarError("Unrecognized RSA Private Key Version") for i in range(len(RSA_PARAMS)): params[RSA_PARAMS[i]] = long(key[i+1]) elif oid == DSA_OID: alg_params = ParseASN1Sequence(alg_params) for i in range(len(DSA_PARAMS)): params[DSA_PARAMS[i]] = long(alg_params[i]) params['x'] = long(key) else: raise errors.KeyczarError("Unrecognized AlgorithmIdentifier: not RSA/DSA") return params def ExportRsaPkcs8(params): oid = ASN1Sequence(RSA_OID, univ.Null()) key = univ.Sequence().setComponentByPosition(0, univ.Integer(0)) # version for i in range(len(RSA_PARAMS)): key.setComponentByPosition(i+1, univ.Integer(params[RSA_PARAMS[i]])) octkey = encoder.encode(key) seq = ASN1Sequence(univ.Integer(0), oid, univ.OctetString(octkey)) return Base64WSEncode(encoder.encode(seq)) def ExportDsaPkcs8(params): alg_params = univ.Sequence() for i in range(len(DSA_PARAMS)): alg_params.setComponentByPosition(i, univ.Integer(params[DSA_PARAMS[i]])) oid = ASN1Sequence(DSA_OID, alg_params) octkey = encoder.encode(univ.Integer(params['x'])) seq = ASN1Sequence(univ.Integer(0), oid, univ.OctetString(octkey)) return Base64WSEncode(encoder.encode(seq)) #NOTE: not full X.509 certificate, just public key info #SubjectPublicKeyInfo ::= SEQUENCE { # algorithm AlgorithmIdentifier, # subjectPublicKey BIT STRING } def ParseX509(x509): seq = ParseASN1Sequence(decoder.decode(Base64WSDecode(x509))[0]) if len(seq) != 2: # need two fields in SubjectPublicKeyInfo raise errors.KeyczarError("Illegal X.509 String.") [oid, alg_params] = ParseASN1Sequence(seq[0]) pubkey = decoder.decode(univ.OctetString(BinToBytes(seq[1]. prettyPrint()[1:-2])))[0] # Component 1 should be a BIT STRING, get raw bits by discarding extra chars, # then convert to OCTET STRING which can be ASN.1 decoded params = {} if oid == RSA_OID: [params['n'], params['e']] = [long(x) for x in ParseASN1Sequence(pubkey)] elif oid == DSA_OID: vals = [long(x) for x in ParseASN1Sequence(alg_params)] for i in range(len(DSA_PARAMS)): params[DSA_PARAMS[i]] = vals[i] params['y'] = long(pubkey) else: raise errors.KeyczarError("Unrecognized AlgorithmIdentifier: not RSA/DSA") return params def ExportRsaX509(params): oid = ASN1Sequence(RSA_OID, univ.Null()) key = ASN1Sequence(univ.Integer(params['n']), univ.Integer(params['e'])) binkey = BytesToBin(encoder.encode(key)) pubkey = univ.BitString("'%s'B" % binkey) # needs to be a BIT STRING seq = ASN1Sequence(oid, pubkey) return Base64WSEncode(encoder.encode(seq)) def ExportDsaX509(params): alg_params = ASN1Sequence(univ.Integer(params['p']), univ.Integer(params['q']), univ.Integer(params['g'])) oid = ASN1Sequence(DSA_OID, alg_params) binkey = BytesToBin(encoder.encode(univ.Integer(params['y']))) pubkey = univ.BitString("'%s'B" % binkey) # needs to be a BIT STRING seq = ASN1Sequence(oid, pubkey) return Base64WSEncode(encoder.encode(seq)) def MakeDsaSig(r, s): """ Given the raw parameters of a DSA signature, return a Base64 signature. @param r: parameter r of DSA signature @type r: long int @param s: parameter s of DSA signature @type s: long int @return: raw byte string formatted as an ASN.1 sequence of r and s @rtype: string """ seq = ASN1Sequence(univ.Integer(r), univ.Integer(s)) return encoder.encode(seq) def ParseDsaSig(sig): """ Given a raw byte string, return tuple of DSA signature parameters. @param sig: byte string of ASN.1 representation @type sig: string @return: parameters r, s as a tuple @rtype: tuple @raise KeyczarErrror: if the DSA signature format is invalid """ seq = decoder.decode(sig)[0] if len(seq) != 2: raise errors.KeyczarError("Illegal DSA signature.") r = long(seq.getComponentByPosition(0)) s = long(seq.getComponentByPosition(1)) return (r, s) def MakeEmsaMessage(msg, modulus_size): """Algorithm EMSA_PKCS1-v1_5 from PKCS 1 version 2""" magic_sha1_header = [0x30, 0x21, 0x30, 0x9, 0x6, 0x5, 0x2b, 0xe, 0x3, 0x2, 0x1a, 0x5, 0x0, 0x4, 0x14] encoded = "".join([chr(c) for c in magic_sha1_header]) + Hash(msg) pad_string = chr(0xFF) * (modulus_size / 8 - len(encoded) - 3) return chr(1) + pad_string + chr(0) + encoded def BinToBytes(bits): """Convert bit string to byte string.""" bits = _PadByte(bits) octets = [bits[8 * i:8 * (i + 1)] for i in range(len(bits) / 8)] return "".join([chr(int(x, 2)) for x in octets]) def BytesToBin(byte_string): """Convert byte string to bit string.""" return "".join([_PadByte(IntToBin(ord(byte))) for byte in byte_string]) def _PadByte(bits): """Pad a string of bits with zeros to make its length a multiple of 8.""" r = len(bits) % 8 return ((8 - r) % 8) * '0' + bits def IntToBin(n): if n == 0 or n == 1: return str(n) elif n % 2 == 0: return IntToBin(n / 2) + "0" else: return IntToBin(n / 2) + "1" def BigIntToBytes(n): """Return a big-endian byte string representation of an arbitrary length n.""" chars = [] while (n > 0): chars.append(chr(n % 256)) n = n >> 8 chars.reverse() return "".join(chars) def IntToBytes(n): """Return byte string of 4 big-endian ordered byte_array representing n.""" byte_array = [m % 256 for m in [n >> 24, n >> 16, n >> 8, n]] return "".join([chr(b) for b in byte_array]) # byte array to byte string def BytesToLong(byte_string): l = len(byte_string) return long(sum([ord(byte_string[i]) * 256**(l - 1 - i) for i in range(l)])) def Xor(a, b): """Return a ^ b as a byte string where a and b are byte strings.""" # pad shorter byte string with zeros to make length equal m = max(len(a), len(b)) if m > len(a): a = PadBytes(a, m - len(a)) elif m > len(b): b = PadBytes(b, m - len(b)) x = [ord(c) for c in a] y = [ord(c) for c in b] z = [chr(x[i] ^ y[i]) for i in range(m)] return "".join(z) def PadBytes(byte_string, n): """Prepend a byte string with n zero bytes.""" return n * '\x00' + byte_string def TrimBytes(byte_string): """Trim leading zero bytes.""" trimmed = byte_string.lstrip(chr(0)) if trimmed == "": # was a string of all zero byte_string return chr(0) else: return trimmed def RandBytes(n): """Return n random bytes.""" # This function requires at least Python 2.4. return os.urandom(n) def Hash(*inputs): """Return a SHA-1 hash over a variable number of inputs.""" md = sha1() for i in inputs: md.update(i) return md.digest() def PrefixHash(*inputs): """Return a SHA-1 hash over a variable number of inputs.""" md = sha1() for i in inputs: md.update(IntToBytes(len(i))) md.update(i) return md.digest() def Encode(s): warnings.warn('Encode() is deprecated, use Base64WSEncode() instead', DeprecationWarning) return Base64WSEncode(s) def Base64WSEncode(s): """ Return Base64 web safe encoding of s. Suppress padding characters (=). Uses URL-safe alphabet: - replaces +, _ replaces /. Will convert s of type unicode to string type first. @param s: string to encode as Base64 @type s: string @return: Base64 representation of s. @rtype: string """ return base64.urlsafe_b64encode(str(s)).replace("=", "") def Decode(s): warnings.warn('Decode() is deprecated, use Base64WSDecode() instead', DeprecationWarning) return Base64WSDecode(s) def Base64WSDecode(s): """ Return decoded version of given Base64 string. Ignore whitespace. Uses URL-safe alphabet: - replaces +, _ replaces /. Will convert s of type unicode to string type first. @param s: Base64 string to decode @type s: string @return: original string that was encoded as Base64 @rtype: string @raise Base64DecodingError: If length of string (ignoring whitespace) is one more than a multiple of four. """ s = ''.join(s.splitlines()) s = str(s.replace(" ", "")) # kill whitespace, make string (not unicode) d = len(s) % 4 if d == 1: raise errors.Base64DecodingError() elif d == 2: s += "==" elif d == 3: s += "=" try: return base64.urlsafe_b64decode(s) except TypeError: # Decoding raises TypeError if s contains invalid characters. raise errors.Base64DecodingError() # Struct packed byte array format specifiers used below BIG_ENDIAN_INT_SPECIFIER = ">i" STRING_SPECIFIER = "s" def PackByteArray(array): """ Packs the given array into a structure composed of a four-byte, big-endian integer containing the array length, followed by the array contents. """ if not array: return '' array_length_header = struct.pack(BIG_ENDIAN_INT_SPECIFIER, len(array)) return array_length_header + array def PackMultipleByteArrays(*arrays): """ Packs the provided variable number of byte arrays into one array. The returned array is prefixed with a count of the arrays contained, in a four-byte big-endian integer, followed by the arrays in sequence, each length-prefixed by PackByteArray(). """ array_count_header = struct.pack(BIG_ENDIAN_INT_SPECIFIER, len(arrays)) array_contents = ''.join([PackByteArray(a) for a in arrays]) return array_count_header + array_contents def UnpackByteArray(data, offset): """ Unpacks a length-prefixed byte array packed by PackByteArray() from 'data', starting from position 'offset'. Returns a tuple of the data array and the offset of the first byte after the end of the extracted array. """ array_len = struct.unpack(BIG_ENDIAN_INT_SPECIFIER, data[offset:offset + 4])[0] offset += 4 return data[offset:offset + array_len], offset + array_len def UnpackMultipleByteArrays(data): """ Extracts and returns a list of byte arrays that were packed by PackMultipleByteArrays(). """ # The initial integer containing the number of byte arrays that follow is redundant. We # just skip it. position = 4 result = [] while position < len(data): array, position = UnpackByteArray(data, position) result.append(array) assert position == len(data) return result def WriteFile(data, loc): """ Writes data to file at given location. @param data: contents to be written to file @type data: string @param loc: name of file to write to @type loc: string @raise KeyczarError: if unable to write to file because of IOError """ try: f = open(loc, "w") f.write(data) f.close() except IOError: raise errors.KeyczarError("Unable to write to file %s." % loc) def ReadFile(loc): """ Read data from file at given location. @param loc: name of file to read from @type loc: string @return: contents of the file @rtype: string @raise KeyczarError: if unable to read from file because of IOError """ try: return open(loc).read() except IOError: raise errors.KeyczarError("Unable to read file %s." % loc) def MGF(seed, mlen): """ Mask Generation Function (MGF1) with SHA-1 as hash. @param seed: used to generate mask, a byte string @type seed: string @param mlen: desired length of mask @type mlen: integer @return: mask, byte string of length mlen @rtype: string @raise KeyczarError: if mask length too long, > 2^32 * hash_length """ if mlen > 2**32 * HLEN: raise errors.KeyczarError("MGF1 mask length too long.") output = "" for i in range(int(math.ceil(mlen / float(HLEN)))): output += Hash(seed, IntToBytes(i)) return output[:mlen] class BufferedIncrementalBase64WSEncoder(codecs.BufferedIncrementalEncoder): """ Web-safe Base64 encodes an input in multiple steps. Each step bar the final one will be sized to ensure no Base64 padding is required. Any unencoded data outside this optimal size will be buffered. """ def _buffer_encode(self, input, errors, final): """ Encodes input and returns the resulting object, buffering any data that is beyond the optimal no-padding length unless final is True Implementation of abstract method in parent. @param input: string to encode as Base64 @type input: string @param errors: required error handling scheme (see IncrementalBase64WSStreamWriter) @param final: force all data to be encoded, possibly resulting in padding #type final: boolean @return: (Base64 representation of input, length consumed) @rtype: tuple """ # Overwrite this method in subclasses: It must encode input # and return an (output, length consumed) tuple if not final: # only output exact multiples of 3-bytes => no padding len_to_write = 3 * (len(input) / 3) else: len_to_write = len(input) return (Base64WSEncode(input[:len_to_write]), len_to_write) def encode(self, input, final=False): """ Encodes input and returns the resulting object. Note that unless final is True the returned data may not encode all the supplied input as it encodes the maximum length that will not result in padding. The remaining data is buffered for subsequent calls to encode(). @param input: string to encode as Base64 @type input: string @param final: force all data to be encoded, possibly resulting in padding #type final: boolean @return: (Base64 representation of input, length consumed) @rtype: tuple """ result = super(BufferedIncrementalBase64WSEncoder, self).encode(input, final=final) return (result, len(input) - len(self.buffer)) def flush(self): """ Flush this encoder, returning any buffered data @return: Base64 representation of buffered data @rtype: string """ result = ('', 0) if self.buffer: result = self._buffer_encode(self.buffer, self.errors, True) self.buffer = '' return result[0] class IncrementalBase64WSStreamWriter(codecs.StreamWriter, object): """ Web-safe Base64 encodes a stream in multiple steps to an output stream. Each step bar the final one will be sized to ensure no Base64 padding is required. Any unencoded data outside this optimal size will be buffered and output when flush() is called. """ def __init__(self, stream, errors='strict'): """ Creates an IncrementalBase64WSStreamWriter instance. @param stream: a file-like object open for writing (binary) data. @param errors: required error handling scheme The reader may use different error handling schemes by providing the errors keyword argument. These parameters are predefined: 'strict' - raise a ValueError (or a subclass) 'ignore' - ignore the character and continue with the next 'replace'- replace with a suitable replacement character 'xmlcharrefreplace' - Replace with the appropriate XML character reference. 'backslashreplace' - Replace with backslashed escape sequences (only for encoding). """ super(IncrementalBase64WSStreamWriter, self).__init__(stream, errors) self.encoder = BufferedIncrementalBase64WSEncoder(errors=errors) def close(self): """ Flushes and closes the stream """ self.flush() super(IncrementalBase64WSStreamWriter, self).close() def flush(self): """ Flush this stream, writing any buffered data to the output stream """ result = self.encoder.flush() if result: self.stream.write(result) self.stream.flush() def encode(self, input, errors='strict'): """ Base64 Encodes input and returns the resulting object. @param input: string to encode as Base64 @type input: string @param errors: required error handling scheme (see __init__) @return: Base64 representation of input. @rtype: string """ return self.encoder.encode(input) class BufferedIncrementalBase64WSDecoder(codecs.BufferedIncrementalDecoder): """ Web-safe Base64 decodes an input in multiple steps. Each step bar the final one will be sized to a length so that no Base64 padding is required. Any undecoded data outside this optimal size will be buffered. """ def _buffer_decode(self, input, errors, final): """ Decodes input and returns the resulting object, buffering any data that is beyond the optimal no-padding length unless final is True Implementation of abstract method in parent. @param input: string to decode from Base64 @type input: string @param errors: required error handling scheme (see IncrementalBase64WSStreamReader) @param final: force all data to be decoded, possibly resulting in padding #type final: boolean @return: (plaintext version of input, length consumed) @rtype: tuple """ if not final: # only output exact multiples of 4-bytes => no padding len_to_read = 4 * (len(input) / 4) else: len_to_read = len(input) return (Base64WSDecode(input[:len_to_read]), len_to_read) def decode(self, input, final=False): """ Decodes input and returns the resulting object. Note that unless final is True the returned data may not decode all the supplied input as it uses the maximum length that would not require padding. The remaining data is buffered for subsequent calls to decode(). @param input: string to decode from Base64 @type input: string @param final: force all data to be decoded, possibly resulting in padding #type final: boolean @return: plaintext representation of input. @rtype: string """ result = super(BufferedIncrementalBase64WSDecoder, self).decode(input, final=final) return (result, len(input)) def flush(self): """ Flush this decoder, returning any buffered data @return: plaintext representation of buffered data @rtype: string """ result = ('', 0) if self.buffer: result = self._buffer_decode(self.buffer, self.errors, True) self.buffer = '' return result[0] class IncrementalBase64WSStreamReader(codecs.StreamReader, object): """ Web-safe Base64 decodes a stream in multiple steps. Each step bar the final one will be sized to a length so that no Base64 padding is required. Any undecoded data outside this optimal size will be buffered and decoded on a final call to read(). """ def __init__(self, stream, errors='strict'): """ Creates an IncrementalBase64WSStreamReader instance. @param stream: a file-like object open for reading (binary) data. @param errors: required error handling scheme The reader may use different error handling schemes by providing the errors keyword argument. These parameters are predefined: 'strict' - raise a ValueError (or a subclass) 'ignore' - ignore the character and continue with the next 'replace'- replace with a suitable replacement character 'xmlcharrefreplace' - Replace with the appropriate XML character reference. 'backslashreplace' - Replace with backslashed escape sequences (only for encoding). """ super(IncrementalBase64WSStreamReader, self).__init__(stream, errors) self.decoder = BufferedIncrementalBase64WSDecoder(errors=errors) def read(self, size=-1, chars=-1, firstline=False): """ Decodes data from the input stream and returns the resulting object. @param chars: the number of characters to read from the stream. read() will never return more than chars characters, but it might return less, if there are not enough characters available. Will return None if the underlying stream does i.e. is non-blocking and no data is available. @type chars: integer @param size: indicates the approximate maximum number of bytes to read from the stream for decoding purposes. The decoder can modify this setting as appropriate. The default value -1 indicates to read and decode as much as possible. size is intended to prevent having to decode huge files in one step. @type size: integer @param firstline: if firstline is true, and a UnicodeDecodeError happens after the first line terminator in the input only the first line will be returned, the rest of the input will be kept until the next call to read(). @type firstline: boolean @return: plaintext representation of input. Returns an empty string when the end of the input data has been reached. @rtype: string """ # NOTE: this is a copy of the code from Python v2.7 codecs.py tweaked to # handle non-blocking streams i.e. those that return None to indicate no # data is available but is not at EOF - see read() in the Python I/O module # documentation. # === start of codecs.py code === # If we have lines cached, first merge them back into characters if self.linebuffer: self.charbuffer = "".join(self.linebuffer) self.linebuffer = None # read until we get the required number of characters (if available) newdata = '' while True: # can the request can be satisfied from the character buffer? if chars < 0: if size < 0: if self.charbuffer: break elif len(self.charbuffer) >= size: break else: if len(self.charbuffer) >= chars: break # we need more data try: if size < 0: newdata = self.stream.read() else: newdata = self.stream.read(size) except BlockingIOError: newdata = None if newdata is not None: # decode bytes (those remaining from the last call included) data = self.bytebuffer + newdata try: newchars, decodedbytes = self.decode(data, self.errors) except UnicodeDecodeError, exc: if firstline: newchars, decodedbytes = self.decode(data[:exc.start], self.errors) lines = newchars.splitlines(True) if len(lines)<=1: raise else: raise # keep undecoded bytes until the next call self.bytebuffer = data[decodedbytes:] # put new characters in the character buffer self.charbuffer += newchars # there was no data available if not newdata: break if chars < 0: # Return everything we've got result = self.charbuffer self.charbuffer = "" else: # Return the first chars characters result = self.charbuffer[:chars] self.charbuffer = self.charbuffer[chars:] # === end of codecs.py code === if not result and newdata is None: result = None if not result and newdata == '': if chars != 0: result = self.decoder.flush() return result def decode(self, input, errors='strict'): """ Decodes Base64 input and returns the resulting object. @param input: string to decode from Base64 @type input: string @param errors: required error handling scheme (see __init__) @return: plaintext representation of input. @rtype: string """ return self.decoder.decode(input) def Memoize(func): """ General-purpose memoization decorator. Handles functions with any number of arguments, including keyword arguments. """ memory = {} @functools.wraps(func) def memo(*args,**kwargs): pickled_args = cPickle.dumps((args, sorted(kwargs.iteritems()))) if pickled_args not in memory: memory[pickled_args] = func(*args,**kwargs) return memory[pickled_args] if memo.__doc__: memo.__doc__ = "\n".join([memo.__doc__,"This function is memoized."]) return memo def ImportAll(pluginpath): """ Simple plugin importer - imports from the specified subdirectory under the util.py directory @param subdir: the subdirectory to load from @type subdir: string """ if os.path.exists(pluginpath): pluginfiles = [fname[:-3] for fname in os.listdir(pluginpath) if fname.endswith(".py")] if not pluginpath in sys.path: sys.path.append(pluginpath) imported_modules = [__import__('%s' %(fname)) for fname in pluginfiles] def ImportBackends(): """ Simple backend plugin importer - imports from the 'backends' subdirectory under the util.py directory and any directories in the environment variable 'KEYCZAR_BACKEND_PATHS', which can contain >=1 paths joined using the o/s """ pluginpath = os.path.join(os.path.dirname(__file__), 'backends') ImportAll(pluginpath) xtra_paths = os.environ.get(BACKEND_PATHS_ENV_VAR, '') if xtra_paths: for path in xtra_paths.split(os.pathsep): ImportAll(path)