Mercurial > repos > cathywise > truststore_import
diff PythonTrustStore-0.2.0/py_ts/TrustStoreClient.py @ 1:ff126718bdc5
Uploaded
author | cathywise |
---|---|
date | Wed, 11 Dec 2013 21:05:12 -0500 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PythonTrustStore-0.2.0/py_ts/TrustStoreClient.py Wed Dec 11 21:05:12 2013 -0500 @@ -0,0 +1,1601 @@ +import requests +from requests_oauth2 import OAuth2 +import simplejson as json +import simplejson.scanner +from urllib import quote +from urlparse import parse_qs, urljoin +from functools import wraps +from tempfile import NamedTemporaryFile +# from subprocess import call, check_output, Popen +import subprocess +import os +from urlparse import urlparse +from datetime import datetime +import xmltodict +import uuid +import webbrowser +import base64 +import binascii +import hashlib +import textwrap +import re +import boto.exception +import boto.s3.connection as botoConn +from boto.s3.key import Key as botoKey +from distutils.version import LooseVersion +from parts import * +# import threading +from multiprocessing.pool import ThreadPool +# import Queue +from passlib.hash import pbkdf2_sha256 +from passlib.utils import ab64_decode +from itertools import izip_longest +import sys + +# import cProfile #, pstats + + +DASHES = "-----" +BEGIN = "BEGIN" +END = "END" +PUBLIC = "PUBLIC" +PRIVATE = "PRIVATE" +KEY = "KEY" +CERTIFICATE = "CERTIFICATE" + +class TrustStoreClient(object): + """A client for connecting to a TrustStore service, and probably a storage provider. Currently supports S3 and S3-like storage providers (such as Nectar) which are supported by the Python boto library. Requires a version of OpenSSL greater than 1.0, i.e. Mac will have to install using homebrew. + + """ + brewOpenSSL = '/usr/local/Cellar/openssl/' + jsonHeaders = {'Content-type': 'application/json', 'Accept': 'application/json'} + storesPrefix = "/store" + storesList = "/all" + filesPrefix = "/file" + keysPrefix = "/keys" + publicKeysPrefix = "/public_key" + privateKeysPrefix = "/private_key" + usernamesPrefix = "/username" + saltPrefix = "/loginsalt" + passwordResetPrefix = "/credentials" + + providers = [] + + pieceSize = 209715 + + openSSL = "openssl" + + kmsUrl = u'http://localhost:8080/TSSKeyManagementService-Collaboration' + imsUrl = u'http://localhost:8080/TSSIntegrityManagementService/services/IMS' + client_key = u'my-trusted-client-with-secret' + client_secret = u'somesecret' + headless = False + redirect_uri = "oob" + keyFile = None + auth = None + kmsClient = None + username = None + + def requiresAuth(func): + """Wraps a function which requires the client be already sucesfully authenticated with the Key Management Service.""" + @wraps(func) + def withAuth(self, *args, **kwargs): + if self.kmsClient: + return func(self, *args, **kwargs) + else: + raise TrustStoreClientAuthenticationException("Not authenticated!") + return withAuth + + def __init__(self, headless, config=None, accessToken=None): + """Set up the client, checking OpenSSL + + :param headless: Are we running somewhere where opening a browser window would be a bad idea? If we're on a server, this should always be True + :type headless: boolean + :param config: Optional collection of configuration variables, must have values kmsUrl, imsUrl, client_key and client_secret. + :param accessToken: If being re-run, an accessToken can be supplied and autentication skipped. Note that if you're doing this you're expected to have set the username and password properties manually. In which case you may as well just authenticate. + + """ + if os.path.exists(self.brewOpenSSL): + versions = os.listdir(self.brewOpenSSL) + versions.sort(key=LooseVersion) + self.openSSL = self.brewOpenSSL + versions[-1] + "/bin/openssl" + + sslVersion = subprocess.check_output([self.openSSL, 'version']) + m = re.search('([0-9]+.[0-9]+.[0-9]+[a-z]*)', sslVersion) + if LooseVersion(m.group(0)) < LooseVersion("1.0.0"): + raise OpenSSLVersionException(m.group(0)) + elif not headless: + print "Working with " + sslVersion + + if config: + self.kmsUrl = config.kmsUrl + self.imsUrl = config.imsUrl + self.client_key = config.client_key + self.client_secret = config.client_secret + self.headless = headless + self.auth = OAuth2(self.client_key, self.client_secret, self.kmsUrl, self.redirect_uri) + if accessToken: + self.kmsClient = requests.session(params={'access_token': accessToken}) + self.username = self._getUsername() + + def authenticate(self, username=None, password=None): + """Talk to KMS and get the required tokens etc. If the username and password are not supplied OAuth-2 token authentication is attempted. In headless mode thiis will cause a url to be returned where the user will have to login and authorize this application. Will throw exceptions if credentials are rejected or KMS cannot be reached. + + :param username: The username already registered with KMS. + :type username: string/unicode + :param password: The user's KMS password. + :type password: string/unicode + :rtype: None or string (authorisation url) + + """ + + if not self.kmsClient: + response = None + if not username or not password: + authorization_url = self.auth.authorize_url(scope='read trust write', response_type='code') + if authorization_url: + if self.headless: + return authorization_url + else: + webbrowser.open(authorization_url) + code = raw_input('Code:') + response = self.auth.get_token(code, grant_type='authorization_code') + else: + self.username = username + self.password = password + ans = requests.get("%s%s/%s" %(self.kmsUrl, self.saltPrefix, username)) + salt = base64.b64decode(ans.text) + # Passlib uses a *custom* base64 encoding because it is arse, so fix to normal. + sendPassword = base64.b64encode(ab64_decode(pbkdf2_sha256.encrypt(username + password, rounds=1000, salt=salt).split("$")[-1])) + login_form = {'username': username, + 'password': sendPassword, + 'client_id': self.client_key, + 'client_secret': self.client_secret, + 'grant_type': 'password'} + response = requests.post("%s%s" % (self.auth.site, quote(self.auth.token_url)), data=login_form, allow_redirects=True) + + if isinstance(response.content, basestring): + try: + response = json.loads(response.content) + except ValueError: + response = parse_qs(response.content) + else: + response = response.content + + if response: + try: + self.kmsClient = requests.session(params={'access_token': response['access_token']}) + if not self.username: + self.username = self._getUsername() + except KeyError: + raise TrustStoreClientAuthenticationException("Credentials not accepted. Response: " + str(response)) + else: + raise TrustStoreClientAuthenticationException("Communication error with TrustStore server. Is it running?") + + def twoStageAuth(self, code): + """If using oAuth2, this method will need to be called after :func:`authenticate` + + :param code: The oAuth2 token. + """ + + response = self.auth.get_token(code) + self.kmsClient = requests.session(params={'access_token': response['access_token']}) + self.username = self._getUsername() + + def clearAuth(self): + """Clear any authentication (username/password) but preserve other configuration (server locations etc)""" + self.auth = None + self.kmsClient = None + self.username = None + + def addProvider(self, prov): + """Tell the client about a new provider to use (in addition) + + :param prov: The new provider + :type prov: `Provider` + + """ + self.providers.append(prov) + + @requiresAuth + def listStores(self): + """List all the stores this user has access to + + :rtype: list of Store objects + + """ + stores = [] + r = self.kmsClient.get(self.kmsUrl + self.storesPrefix) # + self.storesList) + if 'error' not in r.json: + for store in r.json: + stores.append(Store(store)) + return stores + else: + print r.text + print r.json + + @requiresAuth + def getPrivateKey(self, filename): + """Fetch the user's private key from KMS.""" + getr = self.kmsClient.get(self.kmsUrl + self.privateKeysPrefix + "/" + self.username) + if getr.json and 'error' not in getr.json: + key = UserPrivateKey(getr.json) + if key.certificate: + with open(filename, 'w+') as f: + f.write(key.certificate) + self.keyFile = filename + else: + self.setNewKey(filename) + return filename + else: + print "Couldn't find private key?" + print getr.text + + @requiresAuth + def setNewKey(self, filename): + """Generate a new private key, either because the user doesn't have one yet, or they've requested a new one.""" + print "No private key found!" + self._generateKeypair(filename) + keydata = None + if filename: + with open(filename, 'r') as f: + keydata = f.read() + key = UserPrivateKey(None, self.username, keydata) + postr = self.kmsClient.post(self.kmsUrl + self.privateKeysPrefix, data=json.dumps(key.dict()), headers=self.jsonHeaders) + if postr.status_code != requests.codes.ok: + print "Server refused to save new private key." + return + return filename + + @requiresAuth + def getStore(self, name): + """Get the first store (that the user has access to) matching the specified name.""" + for store in self.listStores(): + if store.friendly_name == name: + return store + + @requiresAuth + def createStore(self, store=None, name=None): + """Create a new store. + + :param store: Pre-prepared store to save to KMS. + :type store: Store + :param name: If no pre-prepared store, create new store with this name. If not supplied, store name will be "default". + :type name: string/unicode + :rtype: Store + + """ + if not store: + if not name: + name = "default" + store = Store(owner=self._getUsername(), friendly_name=name) + store.administrators.append(self._getUsername()) + + storejson = json.dumps(store.dict()) + # print storejson + postr = self.kmsClient.post(self.kmsUrl + self.storesPrefix, data=storejson, headers=self.jsonHeaders) + if postr.status_code != requests.codes.ok: + print "Server refused request to save store description." + print postr.status_code + print postr.text + return + + store = Store(postr.json) + storeFile = StoreProperties() + username = str(uuid.uuid4()) + password = binascii.b2a_hex(os.urandom(20)) + storeFile.ims_url = self.imsUrl + storeFile.kms_url = self.kmsUrl + storeFile.ims_user = {"ident": username, "secret": password} + + storeFile.providers = self.providers + self._createNewBucket(storeFile) + self._createAndStoreEmptyDirectory(store, storeFile) + + privateKeyFile = self._generatePKCS1Keypair() + if privateKeyFile: + publicKeyFile = self.__publicKeyFromPrivate(privateKeyFile) + storeFile.private_key_bytes = self.__readPrivateKeyFromFile(privateKeyFile) + storeFile.public_key_bytes = self.__readPublicKeyFromFile(publicKeyFile) + # print storeFile.private_key_bytes + # print storeFile.public_key_bytes + imsPublicKey = self._imsRegister(username, password, self.__readPublicKeyFromFile(publicKeyFile)) + storeFile.ims_public_key_bytes = imsPublicKey + # print imsPublicKey + _json = postr.json + if self._putStoreFile(store, storeFile): + putr = self.kmsClient.put(self.kmsUrl + self.storesPrefix, data=json.dumps(store.dict()), headers=self.jsonHeaders) + if putr.status_code != requests.codes.ok: + print "Sever refused request to save store file." + print putr.status_code + print putr.text + else: + _json = putr.json + self.__remove(publicKeyFile) + self.__remove(privateKeyFile) + return Store(_json) + + @requiresAuth + def updateStore(self, store): + """Update a store (where root file or permissions have changed, pressumably).""" + putr = self.kmsClient.put(self.kmsUrl + self.storesPrefix, data=json.dumps(store.dict()), headers=self.jsonHeaders) + if putr.status_code != requests.codes.ok: + print putr.status_code + print putr.text + else: + # update access + storeFile = self._getStoreFile(store) + self._putStoreFile(store, storeFile) + return Store(putr.json) + + @requiresAuth + def deleteStore(self, store): + """Delete a store (with the same id as this store, anyway). This is not recoverable!!""" + url = self.kmsUrl + self.storesPrefix + "/" + str(store.id) + delr = self.kmsClient.delete(url) + if delr.status_code != requests.codes.ok: + print delr.status_code + print url + print delr.text + + @requiresAuth + def changePassword(self, newPassword): + """Change the user's password to a new one. This requires the old password be known!""" + newKeyFile = self._changeKeyPassword(self.password, newPassword) + key = None + with open(newKeyFile, 'rb') as f: + key = f.read() + ans = requests.get("%s%s/%s" %(self.kmsUrl, self.saltPrefix, self.username)) + salt = base64.b64decode(ans.text) + sendNewPassword = base64.b64encode(ab64_decode(pbkdf2_sha256.encrypt(self.username + newPassword, rounds=1000, salt=salt).split("$")[-1])) + sendOldPassword = base64.b64encode(ab64_decode(pbkdf2_sha256.encrypt(self.username + self.password, rounds=1000, salt=salt).split("$")[-1])) + reset = {"password":sendNewPassword, "oldPassword":sendOldPassword, "key":{"username":self.username, "key": key}} + postr = self.kmsClient.post(self.kmsUrl + self.passwordResetPrefix, data=json.dumps(reset), headers=self.jsonHeaders) + if postr.status_code != requests.codes.ok: + print postr.status_code + print postr.text + + @requiresAuth + def listDirectory(self, store): + """List all the files in the given store. + + :rtype: Directory + + """ + directory = None + makeDirectory = False + storeFile = self._getStoreFile(store) + if storeFile and storeFile.bucket: + provider = self._getCloudService(storeFile) + tmpRoot = self._getPartFromCloud(store.index_codename, provider, storeFile.bucket) + if tmpRoot: + key = self._getKeyForFragment(store.index_codename, store.id) + if key: + tmpRoot = self._decryptPart(tmpRoot, key, store.iv) + if tmpRoot: + jsonText = "" + with open(tmpRoot) as f: + jsonText = f.read() + directory = Directory(jsonText=jsonText) + self.__remove(tmpRoot) + else: + makeDirectory = True + else: + makeDirectory = True + self.__remove(tmpRoot) + else: + makeDirectory = True + elif storeFile and len(storeFile.providers) > 0: + self._createNewBucket(storeFile) + makeDirectory = True + else: + print "has no bucket" + print storeFile + + if makeDirectory: + print "creating empty directory" + directory = self._createAndStoreEmptyDirectory(store, storeFile) + self.updateStore(store) + return directory + + @requiresAuth + def updateDirectory(self, directory, store): + # Check for conflicts!! + newName = unicode(uuid.uuid4()) + storeFile = self._getStoreFile(store) + provider = self._getCloudService(storeFile) + tmpText = json.dumps(directory.dict()) + tmpRoot = self.__temporaryFileWithBytes(tmpText) + key = self._generateKey() + store.iv = self._generateIV() + tmpRoot = self._encryptPart(tmpRoot, key, store.iv) + self._putPartInCloud(newName, provider, storeFile.bucket, tmpRoot) + self._setKeyForFragment(newName, key, store.id) + store.index_codename = newName + self.updateStore(store) + self.__remove(tmpRoot) + pass + + @requiresAuth + def getFile(self, store, files, threads=10): + storeFile = self._getStoreFile(store) + provider = self._getCloudService(storeFile) + if files: + keySets = self._getKeysForFragments(files.fragments, store.id) + if keySets: + clearFile = self.__temporaryFile() + with open(clearFile, 'r+b') as f: + junk = b'\x00' * (files.remote_size) + f.write(junk) + # with open(clearFile, 'wb') as f: + promises = []; + pool = ThreadPool(processes=threads) + for fragmentName in keySets: + fragment = None + for frag in files.fragments: + if frag.name == fragmentName: + fragment = frag + + promise = pool.apply_async(self._doFragmentDownload, [clearFile, fragmentName, provider, storeFile, keySets, fragment]) + promises.append(promise) + for order, promise in enumerate(promises): + self.__graphPrinter(order, len(promises)) + success = promise.get() + self.__graphPrinter(order + 1, len(promises)) + + if not success: + print "Error on part!" + break + files.local_path = clearFile + sys.stdout.write("\n") + sys.stdout.flush() + return clearFile + + def __graphPrinter(self, done, total): + completed = int(((done) * 100.0) / total) + sys.stdout.write(" |" + "=" * completed + "-" * (100 - completed) + "| \r") + sys.stdout.flush() + + @requiresAuth + def getBytes(self, start, end, store, files): + storeFile = self._getStoreFile(store) + provider = self._getCloudService(storeFile) + fragments = files.fragments + allData = None + sizeSoFar = 0 + for fragment, idx in enumerate(fragments): + if sizeSoFar + fragment.length >= start and sizeSoFar <= end: + # Get this fragment + key = self._getKeyForFragment(fragment, store.id) + isOkay = False + attempts = 0 + while not isOkay and attempts < 10: + tmpPart = self._getPartFromCloud(fragment.name, provider, storeFile.bucket) + if tmpPart: + isOkay = self._getVerifyPart(tmpPart, fragment.name, storeFile) + attempts += 1 + else: + break + if isOkay: + tmpPart2 = self._decryptPart(tmpPart, key, fragment.iv) + self.__remove(tmpPart) + if tmpPart2: + with open(tmpPart2, 'rb') as t: + if sizeSoFar <= start: + t.seek(start - sizeSoFar) + thisMuch = fragment.length + if sizeSoFar + fragment.length < end: + thisMuch = (sizeSoFar + fragment.length) - end + allData += t.read(thisMuch) + self.__remove(tmpPart2) + else: + print "File corrupt." + break + + sizeSoFar += fragment.length + return allData + + @requiresAuth + def updateFile(self, store, file_, path, directory): + """ + Update a file on TrustStore. + + :type store: Store + :param store: a store to upload the file to + + :type file_: File + :param file_: the File object to upload. Must have local_path + + :type path: Directory + :param path: the child folder to upload the File to. May be same as directory + + :type directory: Directory + :param directory: root folder + """ + # pr = cProfile.Profile() + # pr.enable() + print file_.name + if file_.remote_size <= 0: + file_.remote_size = os.path.getsize(file_.local_path) + if store and file_ and path and directory: + storeFile = self._getStoreFile(store) + if storeFile: + provider = self._getCloudService(storeFile) + fileSize = os.path.getsize(file_.local_path) + readFragments = 0 + displacement = 0 + optimalSize = self.__optimalPieceSize(fileSize) + promises = []; + pool = ThreadPool(processes=10) + if len(file_.fragments) > 0: + self._deleteKeysForFragments(file_.fragments, store.id) + count404 = 0 + for fragment in file_.fragments: + if self._deletePartFromCloud(fragment.name, provider, storeFile.bucket) == "404": + count404 += 1 + sys.stdout.write(" Trying to delete old file. Parts not found in cloud: " + str(count404) + " of " + str(len(file_.fragments)) + "\r") + sys.stdout.flush() + file_.fragments = [] + while fileSize > readFragments * optimalSize: + sys.stdout.write(" " + str(readFragments) + " optimal: " + str(optimalSize) + " file size: " + str(fileSize) + "\r") + sys.stdout.flush() + promise = pool.apply_async(self._doFragmentUpload, [file_.local_path, optimalSize, readFragments, displacement, storeFile, store.id, provider]) + readFragments += 1 + # ) # + displacement += optimalSize + promises.append(promise) + for order, promise in enumerate(promises): + self.__graphPrinter(order, len(promises)) + part = promise.get() + if part: + file_.fragments.append(part) + else: + print "Part broken?" + break + self.__graphPrinter(order + 1, len(promises)) + isUpdate = False + for child in path.children: + if file_.name == child.name: + # Assume update. + child = file_ + isUpdate = True + if not isUpdate: + path.children.append(file_) + # print directory + self.updateDirectory(directory, store) + sys.stdout.write("\n") + sys.stdout.flush() + else: + print "Hey, you can't upload that! : " + file_.local_path + # pr.disable() + # pr.print_stats(1) + + @requiresAuth + def delFile(self, store, fileName, path, directory): + if store and fileName and path and directory: + for child in path.children: + print child.name + path.children[:] = [child for child in path.children if self._deleteChildren(fileName, child, store)] + self.updateDirectory(directory, store) + elif store and path and directory: + for child in path.children: + print child.name + self._deleteChildren(None, child, store) + path.children = [] + self.updateDirectory(direcroty, store) + else: + print "Not enough information to delete!" + + @requiresAuth + def addFile(self, store, file_, path, directory): + if store and file_ and path and directory and os.path.exists(file_.local_path): + self.updateFile(store, file_, path, directory) + else: + print "Hey, you can't upload that! " + file_.local_path + + ## Past here should no user go. + + def _createAndStoreEmptyDirectory(self, store, storeFile): + directory = Directory() + newName = unicode(uuid.uuid4()) + store.iv = self._generateIV() + provider = self._getCloudService(storeFile) + tmpText = json.dumps(directory.dict()) + tmpRoot = self.__temporaryFileWithString(tmpText) + key = self._generateKey() + tmpRoot = self._encryptPart(tmpRoot, key, store.iv) + self._putPartInCloud(newName, provider, storeFile.bucket, tmpRoot) + self._setKeyForFragment(newName, key, store.id) + store.index_codename = newName + self.__remove(tmpRoot) + return directory + + def _createNewBucket(self, storeFile): + storeFile.bucket = "truststore-" + str(uuid.uuid4()) + provider = self._getCloudService(storeFile) + provider.create_bucket(storeFile.bucket) + + + def _deleteChildren(self, fileName, child, store): + if fileName == child.name or fileName == None: + storeFile = self._getStoreFile(store) + if storeFile: + try: + self._deleteKeysForFragments(child.fragments, store.id) + provider = self._getCloudService(storeFile) + count404 = 0 + for fragment in child.fragments: + if self._deletePartFromCloud(fragment.name, provider, storeFile.bucket) == "404": + count404 += 1 + sys.stdout.write(" Trying to delete old file. Parts not found in cloud: " + str(count404) + " of " + str(len(child.fragments)) + "\r") + sys.stdout.flush() + child.fragments = [] + print "\n" + print "Deleted file " + fileName + except AttributeError: + for subs in child.children: + print subs.name + self._deleteChildren(None, subs, store) + return False + else: + return True + + def _doFragmentDownload(self, filename, fragmentName, provider, storeFile, keySets, fragment): + isOkay = False + tries = 0 + sucess = False + tmpPart = None + while not isOkay and tries < 10: + tmpPart = self._getPartFromCloud(fragmentName, provider, storeFile.bucket) + if tmpPart: + isOkay = self._getVerifyPart(tmpPart, fragmentName, storeFile) + tries += 1 + else: + break + if isOkay: + tmpPart2 = self._decryptPart(tmpPart, keySets[fragmentName], fragment.iv) + if tmpPart2: + with open(tmpPart2, 'rb') as t: + currentSize = os.path.getsize(filename) + data = t.read() + if currentSize <= fragment.offset: + with open(filename, 'a+b') as f: + if currentSize < fragment.offset: + sys.stdout.write(" !! :(") + junk = b'\x00' * (fragment.offset - currentSize) + f.write(junk) + f.write(data) + sys.stdout.write(" Piece " + str(fragment.order) + " \t\t\r") + sys.stdout.flush() + sucess = True + else: + with open(filename, 'r+b') as f: + f.seek(fragment.offset, 0) + sys.stdout.write(" Piece " + str(fragment.order) + " \t\t\r") # + ": " + str(f.tell()) + ": " + str(len(data)) + "\r") + sys.stdout.flush() + f.write(data) + sucess = True + self.__remove(tmpPart2) + else: + print "Part broken!!" + self.__remove(tmpPart) + return sucess + + def _doFragmentUpload(self, localPath, optimalSize, readFragments, displacement, storeFile, storeId, provider): + fragment = Fragment() + with open(localPath, 'rb') as f: + fragment.length = optimalSize + fragment.order = readFragments + fragment.providers = storeFile.providers + fragment.offset = displacement + f.seek(displacement) + data = f.read(fragment.length) + # sys.stdout.write(" " + str(fragment.order) + " displacement: " + str(displacement) + " length: " + str(fragment.length) + # + "\r") + # sys.stdout.flush() + if len(data) < fragment.length: + fragment.length = len(data) + # sys.stdout.write(" " + str(fragment.order) + " true length: " + str(len(data)) + " \r") + sys.stdout.flush() + + tmpDataFile = self.__temporaryFileWithBytes(data) + key = self._generateKey() + fragment.name = unicode(uuid.uuid4()) + fragment.iv = self._generateIV() + encryptedPart = self._encryptPart(tmpDataFile, key, fragment.iv) + self._putPartInCloud(fragment.name, provider, storeFile.bucket, encryptedPart) + self._setKeyForFragment(fragment.name, key, storeId) + self._storeVerifyPart(encryptedPart, fragment.name, storeFile) + # sys.stdout.write(" Finished fragment " + unicode(readFragments) + " \r") + sys.stdout.flush() + self.__remove(tmpDataFile) + self.__remove(encryptedPart) + return fragment + + def _getStoreFile(self, store): + getr = self.kmsClient.get(self.kmsUrl + self.filesPrefix + "/" + unicode(store.id)) # + "/" + unicode(store.filename)) + message = getr.content + if message: + clear = self._decryptStoreFileUsingPrivateKeyfile(message, self.keyFile) + if clear: + # print clear + try: + storefile = json.loads(clear) + storeProps = StoreProperties(eDict=storefile) + # print json.dumps(storeProps.dict()) + return storeProps + except simplejson.scanner.JSONDecodeError: + print "This profile is probably XML." + else: + print "Failed to decrypt profile, download status code: " + str(getr.status_code) + + def _putStoreFile(self, store, storeFile): + success = False + userList = [] + userList += store.readers + userList += store.writers + userList += store.administrators + userList.append(store.owner) + userList = list(set(userList)) + storeDict = storeFile.dict() + messageFile = self._encryptStoreFileForUsers(json.dumps(storeDict), userList) + # print json.dumps(storeDict) + if messageFile: + with open(messageFile, 'rb') as f: + message = {'filename': ("file", f)} # this filename "file" is never read but it needs to be there to be valid so it's just whatever. + url = self.kmsUrl + self.filesPrefix + "/" + unicode(store.id) + postr = self.kmsClient.post(url, files=message) + # print postr.request.headers + # print postr.request.data + self.__remove(messageFile) + if postr.status_code == requests.codes.ok: + success = True + else: + print postr.status_code + print postr.text + print "Failed to put Store File" + print url + print store.id + return success + + def _getCloudService(self, storeFile): + for provider in storeFile.providers: + api = provider['api'] + if api == "nectar" or api == "s3": + calling = botoConn.SubdomainCallingFormat() + if api == "nectar": + calling = botoConn.OrdinaryCallingFormat() + user = provider['userCredentials'] + endpoint = provider['endpoint'] + connection = botoConn.S3Connection( + aws_access_key_id=user['ident'], + aws_secret_access_key=user['secret'], + port=urlparse(endpoint).port, + host=urlparse(endpoint).hostname, + is_secure=True, + validate_certs=False, + calling_format=calling + ) + return connection + + def _changeKeyPassword(self, old, new): + newKey = self.keyFile + ".new" + openssl = [self.openSSL, 'rsa', '-aes128', '-in', self.keyFile, '-out', newKey, '-passin', 'pass:' + old, '-passout', 'pass:' + new] + okay = subprocess.call(openssl) + if (okay == 0): + self.keyFile = newKey + return newKey + else: + return False + + def _getPublicCertFor(self, username): + getr = self.kmsClient.get(self.kmsUrl + self.publicKeysPrefix + "/" + username) + return UserPublicCertificate(getr.json) + + def _setPublicCert(self, username): + publicCertFile = self.__makeRSACertificateFromPrivate(self.keyFile) + if publicCertFile: + certificate = self.__readCertificateFromFile(publicCertFile) + cert = UserPublicCertificate(None, username, certificate) + headers = {'Content-type': 'application/json', 'Accept': 'application/json'} + self.kmsClient.post(self.kmsUrl + self.publicKeysPrefix, data=json.dumps(cert.dict()), headers=headers) + self.__remove(publicCertFile) + + def _checkPublicCert(self, cert): + print "Checking your certificate..." + certFile = self.__writeCertificateToFile(cert.certificate) + openssl = [self.openSSL, 'x509', '-in', certFile, '-modulus', '-noout'] + # certModulus = subprocess.check_output(openssl) + p = subprocess.Popen(openssl, stdout=subprocess.PIPE) + certModulus = p.communicate()[0] + openssl = [self.openSSL, 'rsa', '-in', self.keyFile, '-modulus', '-noout'] + if self.password: + openssl.append("-passin") + openssl.append("pass:" + self.password) + + keyModulus = subprocess.check_output(openssl) + + if (certModulus == keyModulus): + self.__remove(certFile) + return True + else: + print certFile + print certModulus + print keyModulus + return False + + + def _getKeyForFragment(self, fragmentName, storeID): + getr = self.kmsClient.get(self.kmsUrl + self.keysPrefix + "/" + unicode(storeID), params={'codenames': fragmentName}) + if getr.status_code != requests.codes.ok or not getr.json: + print getr.status_code + print "Failed to get key for fragment" + if not getr.json: + print "No key for that fragment known." + return + return base64.b64decode(getr.json[0]['key']) + + def _getKeysForFragments(self, fragments, storeID): + codenames = [fragment.name for fragment in fragments] + args = [iter(codenames)] * 20 + batches = izip_longest(fillvalue=None, *args) + sets = {} + for batch in batches: + getr = self.kmsClient.get(self.kmsUrl + self.keysPrefix + "/" + unicode(storeID), params={'codenames': batch}) + if getr.json: + sets.update(dict([(key['codename'], base64.b64decode(key['key'])) for key in getr.json])) + elif getr.status_code != requests.codes.ok: + print "Failed to get keys for " + str(len(batch)) + " fragments." + print getr + print getr.text + return sets + + def _setKeysForFragments(self, storeID, keySets): + data = json.dumps([{'codename': fragmentName, 'key': base64.b64encode(keySets[fragmentName]), 'expiryDate': None} for fragmentName in keySets]) + headers = {'Content-type': 'application/json', 'Accept': 'application/json'} + postr = self.kmsClient.post(self.kmsUrl + self.keysPrefix + "/" + unicode(storeID), data=data, headers=headers) + if postr.status_code != requests.codes.ok: + print postr.status_code + print "Failed to set keys for fragments" + + def _deleteKeysForFragments(self, fragments, storeID): + codenames = [fragment.name for fragment in fragments] + url = self.kmsUrl + self.keysPrefix + "/" + unicode(storeID) + args = [iter(codenames)] * 20 + batches = izip_longest(fillvalue=None, *args) + for batch in batches: + try: + delr = self.kmsClient.delete(url, params={'codenames': batch}) + if delr.status_code != requests.codes.ok: + print delr.status_code + print delr.text + print delr.content + print "Failed to delete keys." + except requets.exceptions.ConnectionError as e: + print (e) + print url + print batch + + + def _setKeyForFragment(self, fragmentName, key, storeID): + data = json.dumps([{'codename': fragmentName, 'key': base64.b64encode(key), 'expiryDate': None}]) + headers = {'Content-type': 'application/json', 'Accept': 'application/json'} + postr = self.kmsClient.post(self.kmsUrl + self.keysPrefix + "/" + unicode(storeID), data=data, headers=headers) + if postr.status_code != requests.codes.ok: + print postr.status_code + print postr.text + print "Failed to set key for fragment" + + def _decryptStoreFileUsingPrivateKeyfile(self, message, keyFile): + + publicCertificate = self._getPublicCertFor(self.username).certificate + + clearMessage = "" + messageFileName = self.__temporaryFileWithBytes(message) + decryptedFileName = self.__temporaryFile() + certFileName = self.__writeCertificateToFile(publicCertificate) + + openssl = [self.openSSL, 'cms', '-decrypt', + '-in', messageFileName, + '-out', decryptedFileName, + '-recip', certFileName, + '-inkey', keyFile, + '-inform', 'DER'] + + if self.password: + openssl.append("-passin") + openssl.append("pass:" + self.password) + + # decrypt + # openssl smime -decrypt -in encrypted -out decrypted -recip public_cert -inkey private_key + try: + okay = subprocess.call(openssl) + + if (okay == 0): + with open(decryptedFileName) as decryptFile: + clearMessage = decryptFile.read() + else: + print okay + print "Failed to decrypt store file " + # + messageFileName + except TypeError: + print "Failed to call OpenSSL properly:" + print openssl + + # print certFileName + self.__remove(certFileName) + # print messageFileName + self.__remove(messageFileName) + self.__remove(decryptedFileName) + + return clearMessage + + def _encryptStoreFileForUsers(self, plain, userlist): + certificateList = [self._getPublicCertFor(username).certificate for username in userlist] + certificateFileList = [self.__writeCertificateToFile(cert) for cert in certificateList] + decryptedFileName = self.__temporaryFileWithString(plain) + encryptFileName = self.__temporaryFile() + + argList = [self.openSSL, 'cms', '-encrypt', '-aes128', + '-in', decryptedFileName, + '-out', encryptFileName, + '-outform', 'DER'] + argList += certificateFileList + # print argList + okay = subprocess.call(argList) + + self.__remove(decryptedFileName) + for cert in certificateFileList: + self.__remove(cert) + + if okay != 0: + print okay + print "Failed to encrypt store file" + print argList + return False + else: + for cert in certificateFileList: + self.__remove(cert) + return encryptFileName + + def _getPartFromCloud(self, codename, provider, bucketName): + attempts = 0 + while attempts < 20: + try: + bucket = provider.get_bucket(bucketName) + k = botoKey(bucket) + k.key = codename + tmpPart = self.__temporaryFile() + k.get_contents_to_filename(tmpPart) + return tmpPart + except (boto.exception.S3ResponseError, boto.exception.BotoServerError) as e: + if e.status == 403 or e.status == "403": + sys.stdout.write(" Error talking to " + str(provider) + ", attempt: " + str(attempts + 1) + " (403) \r") + else: + sys.stdout.write(" Error talking to " + str(provider) + ", attempt: " + str(attempts + 1) + ": " + str(e) + " \r") + sys.stdout.flush() + attempts += 1 + + def _putPartInCloud(self, codename, provider, bucketName, part): + attempts = 0 + while attempts < 20: + try: + bucket = provider.get_bucket(bucketName) + k = botoKey(bucket) + k.key = codename + k.set_contents_from_filename(part) + return + except boto.exception.S3ResponseError: + sys.stdout.write(" Encountered a storing error talking to " + str(provider) + ", attempt: " + str(attempts + 1) + "\r") + sys.stdout.flush() + attempts += 1 + + def _deletePartFromCloud(self, codename, provider, bucketName): + attempts = 0 + while attempts < 10: + try: + bucket = provider.get_bucket(bucketName) + k = botoKey(bucket) + k.key = codename + bucket.delete_key(k) + return + except boto.exception.S3ResponseError as e: + if "404" not in str(e): + sys.stdout.write(" Encountered a delete error talking to " + str(provider) + ", attempt: " + str(attempts + 1) + "\r") + sys.stdout.flush() + attempts += 1 + else: + return "404" + + def _decryptPart(self, part, key, iv): + decryptedPart = self.__temporaryFile() + mode = '-aes-128-cbc' + if not iv: + mode = '-aes-128-ecb' + iv = b'\x00' + argList = [self.openSSL, 'enc', mode, '-d', '-in', part, '-out', decryptedPart, '-K', binascii.b2a_hex(key), '-iv', binascii.b2a_hex(iv)] + okay = subprocess.call(argList) + # print argList + + if okay != 0: + print okay + print "Failed to decrypt part" + print argList + self.__remove(decryptedPart) + return + return decryptedPart + + def _encryptPart(self, part, key, iv): + encryptedPart = self.__temporaryFile() + mode = '-aes-128-cbc' + if not iv: + mode = '-aes-128-ecb' + iv = b'\x00' + argList = [self.openSSL, 'enc', mode, '-in', part, '-out', encryptedPart, '-K', binascii.b2a_hex(key), '-iv', binascii.b2a_hex(iv)] + okay = subprocess.call(argList) + # print argList + + if okay != 0: + print okay + print "Failed to encrypt part" + print argList + return encryptedPart + + def _imsRegister(self, username, password, publicKey): + url = urljoin(self.imsUrl, "rest/user/" + username) + headers = {'passwd': password} + # postr = requests.post(url, headers=headers, data=base64.b64encode(publicKey)) + + postr = requests.post(url, headers=headers, data={'passwd': password, 'publicKey': base64.b64encode(publicKey)}) + if postr.text and postr.status_code == 200: + text = self.__cleanString(postr.text) + try: + # print "IMS Public Key" + # print text + return base64.b64decode(text) + except (UnicodeError, TypeError): + print "Error getting IMS Public certificate." + return postr.content + else: + print unicode(postr.status_code) + " received while trying to register with IMS." + print postr.text + + def _getVerifyPart(self, part, partName, storeFile): + isOkay = False + url = urljoin(storeFile.ims_url, "rest/hash/" + storeFile.ims_user['ident']) + headers = {'passwd': storeFile.ims_user['secret']} + params = {"codename": partName} + getr = requests.get(url, headers=headers, params=params) + if getr.text and getr.json: + # try: + # resp = xmltodict.parse(getr.text) + resp = getr.json + # if "html" not in resp and "hashInfo" in resp: + if "doubleSignature" in resp: + # resp = resp["hashInfo"] + keyFile = self.__writePublicKeyToFile(storeFile.public_key_bytes) + + signature = base64.b64decode(resp["doubleSignature"]) + sigFile = self.__temporaryFileWithBytes(signature) + meta = resp["signableMetaInfo"] + plainfile = self.__temporaryFileWithString(meta) + + argList = [self.openSSL, 'dgst', '-sha1', '-verify', keyFile, '-signature', sigFile, plainfile] + okay = subprocess.check_output(argList) + verified = (okay and okay == "Verified OK\n") + + self.__remove(sigFile) + self.__remove(keyFile) + self.__remove(plainfile) + if not verified: + print "Unable to verify part." + return isOkay + + sums, imsSum = "", "" + with open(part, 'rb') as f: + sums = hashlib.md5(f.read()).hexdigest() + imsSum = meta[:meta.find("$")] + if sums == imsSum: + isOkay = True + else: + print imsSum + print sums + else: + print resp + # except Exception as e: + # print url + # print getr.text + # print e + return isOkay + + def _storeVerifyPart(self, part, partName, storeFile): + isOkay = False + headers = {'passwd': storeFile.ims_user['secret']} + sums = "" + with open(part, 'rb') as f: + sums = hashlib.md5(f.read()).hexdigest() + + imsKeyfile = self.__writePublicKeyToFile(storeFile.ims_public_key_bytes) + + signable = sums + "$" + partName + "$" + datetime.utcnow().isoformat() + + signableFile = self.__temporaryFileWithString(signable) + + signable64 = base64.b64encode(signable) + url = urljoin(storeFile.ims_url, "rest/sign/" + signable64) + getr = requests.get(url, headers=headers) + if getr.text: # and "string" in getr.text: + resp = getr.text # xmltodict.parse(getr.text) + resp = resp.replace("\\r\\n", "\n") + signature = base64.b64decode(resp) #["string"]) + # print resp + # print len(signature) + # print len(storeFile.ims_public_key_bytes) + + imsSignatureFile = self.__temporaryFileWithBytes(signature) + + argList = [self.openSSL, 'dgst', '-sha1', '-verify', imsKeyfile, '-signature', imsSignatureFile, signableFile] + okay = subprocess.check_output(argList) + verified = (okay == "Verified OK\n") + self.__remove(imsSignatureFile) + if not verified: + print "Unable to make digest." + print argList + else: + nonce = os.urandom(4) + nonceFile = self.__temporaryFileWithBytes(nonce) + encryptedNonceFile = self.__temporaryFile() + argList = [self.openSSL, 'rsautl', '-encrypt', '-pubin', '-inkey', imsKeyfile, '-in', nonceFile, '-out', encryptedNonceFile] + okay = subprocess.call(argList) + if okay != 0: + print "Unable to encrypt test." + print argList + else: + encryptedNonce = "" + with open(encryptedNonceFile, 'rb') as f: + encryptedNonce = f.read() + + signatureFile = self.__temporaryFile() + privKeyFile = self.__writePrivateKeyToFile(storeFile.private_key_bytes) + argList = [self.openSSL, 'dgst', '-sha1', '-sign', privKeyFile, '-out', signatureFile, signableFile] + okay = subprocess.call(argList) + if okay == 0: + + signature = "" + with open(signatureFile, 'rb') as f: + signature = f.read() + + url = urljoin(storeFile.ims_url, "rest/check/" + storeFile.ims_user['ident']) + params = { + "nonce": base64.b64encode(encryptedNonce), + "doubleSig": base64.b64encode(signature), + "xml": signable + } + getr = requests.get(url, headers=headers, params=params) + # print url + # print headers + # print params + if getr.text: # and "string" in getr.text and xmltodict.parse(getr.text)["string"]: + resp = self.__cleanString(getr.text) + resp = base64.b64decode(resp) #xmltodict.parse(getr.text)["string"]) + + imsEncryptedNonceFile = self.__temporaryFileWithBytes(resp) + + imsNonceFile = self.__temporaryFile() + argList = [self.openSSL, 'rsautl', '-decrypt', '-inkey', privKeyFile, '-out', imsNonceFile, '-in', imsEncryptedNonceFile] + okay = subprocess.call(argList) + if okay != 0: + return isOkay + + imsNonce = "" + with open(imsNonceFile, 'r') as f: + imsNonce = f.read() + + if imsNonce != nonce: + return isOkay + + url = urljoin(storeFile.ims_url, "rest/storehash/" + storeFile.ims_user['ident']) + payload = {"xml": signable, "doublesign": base64.b64encode(signature), "codename": partName} + postr = requests.post(url, headers=headers, params=params, data=payload) + if postr.text and "OK" in postr.text: # and "string" in postr.text + isOkay = True + else: + print postr.text + + self.__remove(imsEncryptedNonceFile) + self.__remove(imsNonceFile) + else: + print getr.text + else: + print "Unable to sign digest." + self.__remove(signatureFile) + self.__remove(privKeyFile) + self.__remove(nonceFile) + self.__remove(encryptedNonceFile) + self.__remove(imsSignatureFile) + else: + print getr.text + self.__remove(imsKeyfile) + self.__remove(signableFile) + return isOkay + + def __temporaryFile(self): + fileName = "" + with NamedTemporaryFile(delete=False) as aFile: + fileName = aFile.name + return fileName + + def __temporaryFileWithString(self, string): + fileName = self.__temporaryFile() + with open(fileName, 'w') as f: + if string: + f.write(string) + return fileName + + def __temporaryFileWithBytes(self, bytes): + fileName = self.__temporaryFile() + with open(fileName, 'wb') as f: + if bytes: + f.write(bytes) + return fileName + + def __writeCertificateToFile(self, cert): + certFile = self.__temporaryFile() + header = DASHES + BEGIN + " " + CERTIFICATE + DASHES + footer = DASHES + END + " " + CERTIFICATE + DASHES + with open(certFile, 'w') as f: + if not cert.startswith(header): + f.write(header + "\n") + formatted = "\n".join([line for line in cert.splitlines() if line]) + f.write(formatted) + if not cert.endswith(footer): + f.write("\n" + footer) + return certFile + + def __readCertificateFromFile(self, file_): + cert = None + with open(file_, 'r') as f: + first = f.readline() + if first == DASHES + BEGIN + " " + CERTIFICATE + DASHES + "\n": + cert = f.readlines() + cert = [line for line in cert if line] + cert = cert[:-1] + cert = "\n".join(cert) + else: + cert = first + f.read() + return cert + + def __writePublicKeyToFile(self, key): + return self.__writeKeyToFile(key, PUBLIC) + + def __readPublicKeyFromFile(self, keyFile): + return self.__readKeyFromFile(keyFile, PUBLIC) + + def __readPrivateKeyFromFile(self, keyFile): + return self.__readKeyFromFile(keyFile, PRIVATE) + + def __writePrivateKeyToFile(self, key): + return self.__writeKeyToFile(key, PRIVATE) + + def __writeKeyToFile(self, key, keyname): + keyFile = self.__temporaryFile() + if not self.__probablyBase64(key): + key = base64.b64encode(key) + publicKey = textwrap.fill(key, 63) + # print "KEY" + # print publicKey + with open(keyFile, 'w') as f: + f.write(DASHES + BEGIN + " " + keyname + " " + KEY + DASHES + "\n") + f.write(publicKey) + f.write("\n" + DASHES + END + " " + keyname + " " + KEY + DASHES) + return keyFile + + def __readKeyFromFile(self, keyFile, keyname): + key = None + with open(keyFile) as f: + key = f.read() + if key.startswith(DASHES + BEGIN + " " + keyname + " " + KEY + DASHES + "\n"): + key = "".join(key.splitlines()[1:-1]) + if "\\r\\n" in key: + key = key.replace("\\r\\n", "") + if self.__probablyBase64(key): + key = base64.b64decode(key) + return key + + def _getUsername(self): + getr = self.kmsClient.get(self.kmsUrl + self.usernamesPrefix) + username = None + if getr.json: + try: + username = getr.json[0] + except KeyError: + print getr.json + if username: + myCert = self._getPublicCertFor(username) + if not myCert.certificate: + self._setPublicCert(username) + elif not self._checkPublicCert(myCert): + print "Woah, your certificate isn't valid!" + if self.headless: + sys.exit(1) + else: + reset = raw_input("A new certificate can be created, but all other users will need to re-add you to any shared stores, and you will lose access to any stores you own. Do you want to make a new certificate? (Y for yes, all other input means no): ") + if reset == 'Y' or reset == 'y' or reset == 'yes' or reset == "YES": + self._setPublicCert(username) + else: sys.exit(0) + + return username + + def _generateKey(self): + """128-bit Pseudo-Random Key""" + return os.urandom(16) + + def _generateIV(self): + """128-bit IV""" + return os.urandom(16) + + def _generateKeypair(self, filename=None, encrypt=True): + if not filename: + filename = self.__temporaryFile() + unencrypted = self.__temporaryFile() + if self.password or not encrypt: + # argList = [self.openSSL, 'genrsa', '-aes128', '-out', filename, '-passout', 'pass:' + self.password, '4096'] + argList = [self.openSSL, 'genrsa', '-out', unencrypted, '4096'] + try: + okay = subprocess.call(argList) + if okay != 0: + self.__remove(unencrypted) + unencrypted = False + filename = False + elif encrypt: + argList = [self.openSSL, 'pkcs8', '-topk8', '-v2', 'aes128', '-in', unencrypted, '-out', filename, '-passout', 'pass:' + self.password] + try: + okay = subprocess.call(argList) + if okay != 0: + filename = False + except TypeError: + filename = False + else: + self.__remove(filename) + filename = unencrypted + except TypeError: + self.__remove(filename) + filename = False + else: + self.__remove(filename) + filename = False + if encrypt: + self.__remove(unencrypted) + return filename + + def _generatePKCS1Keypair(self): + filename = self.__temporaryFile() + certFile = self.__temporaryFile() + confFile = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'certs.conf') + days = 365*20 + argList = [self.openSSL, 'req', '-x509', '-out', certFile, '-newkey', 'rsa:4096', '-keyout', filename, '-days', unicode(days), '-nodes', '-config', confFile] + try: + okay = subprocess.call(argList) + if okay != 0: + self.__remove(filename) + filename = False + except TypeError: + self.__remove(filename) + filename = False + self.__remove(certFile) + return filename + + def __makeRSACertificateFromPrivate(self, privateKeyFile): + certificateFile = self.__temporaryFile() + confFile = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'certs.conf') + days = 365*20 + argList = [self.openSSL, 'req', '-new', '-x509', '-key', privateKeyFile, '-out', certificateFile, '-days', unicode(days), '-nodes', '-config', confFile] + if self.password: + argList.append("-passin") + argList.append("pass:" + self.password) + try: + okay = subprocess.call(argList) + if okay != 0: + self.__remove(certificateFile) + return False + return certificateFile + except TypeError: + print "Error making certficate." + print argList + self.__remove(certificateFile) + + def __publicKeyFromPrivate(self, privateKeyFile): + publicKeyFile = self.__temporaryFile() + argList = [self.openSSL, 'rsa', '-pubout', '-outform', 'DER', '-in', privateKeyFile, '-out', publicKeyFile] + if self.password: + argList.append("-passin") + argList.append("pass:" + self.password) + okay = subprocess.call(argList) + if okay == 0: + return publicKeyFile + self.__remove(publicKeyFile) + return False + + def __probablyBase64(self, s): + return (len(''.join(s.split())) % 4 == 0) and re.match('^[A-Za-z0-9+/]+[=]{0,2}$', s) + + def __cleanString(self, s): + openTag = "<string>" + closeTag = "</string>" + + if s.startswith(openTag): + return s[len(openTag):-len(closeTag)] + s = s.replace("\\r\\n", "\n") + return s + + def __optimalPieceSize(self, totalSize): + Dpieces = totalSize / 100 + if Dpieces < self.pieceSize: + return self.pieceSize + return Dpieces + + def __remove(self, path): + try: + os.remove(path) + except OSError: + pass + +class Store(object): + id = None + index_codename = None + friendly_name = None + owner = None + iv = None + # readers = [] + # writers = [] + # administrators = [] + + def __init__(self, props=None, index_codename="", friendly_name="", filename="", owner="", readers=None, writers=None, administrators=None, iv=None): + self.readers = [] + self.writers = [] + self.administrators = [] + if props: + self.id = props['id'] + self.index_codename = props['indexCodename'] + self.friendly_name = props['friendly_name'] + self.owner = props['owner'] + self.readers = props['readers'] + self.writers = props['writers'] + self.administrators = props['administrators'] + if props['iv']: + self.iv = base64.b64decode(props['iv']) + else: + self.index_codename = index_codename + self.friendly_name = friendly_name + # self.filename = filename + self.owner = owner + if readers: + self.readers = readers + if writers: + self.writers = writers + if administrators: + self.administrators = administrators + self.iv = iv + + def __str__(self): + message = u'\n' + unicode(self.friendly_name) + message += u'\n\tOwner: ' + unicode(self.owner) + message += u'\n\tReaders: ' + unicode(self.readers) + message += u'\n\tWriters: ' + unicode(self.writers) + message += u'\n\tAdmins: ' + unicode(self.administrators) + return message + + def __repr__(self): + return str(self) + + def dict(self): + this = { + "id": self.id, + "indexCodename": self.index_codename, + "friendly_name": self.friendly_name, + # "fileName": self.filename, + "owner": self.owner, + "readers": self.readers, + "writers": self.writers, + "administrators": self.administrators + } + if self.iv: + this["iv"] = base64.b64encode(self.iv) + return this + + +class StoreProperties(object): + sas_url = "" + providers = [] + kms_url = "" + kms_user = None + ims_url = "" + ims_user = None + bucket = None + public_key_bytes = None + private_key_bytes = None + ims_public_key_bytes = None + + def __init__(self, eDict=None): + self.providers = [] + if eDict: + self.kms_url = eDict['kmsServiceUrl'] + self.private_key_bytes = base64.b64decode(eDict['privateKeyBytes']) + self.sas_url = eDict['sasServiceUrl'] + self.ims_user = eDict['imsUser'] + self.providers = eDict['storageProviders'] + self.public_key_bytes = base64.b64decode(eDict['publicKeyBytes']) + self.ims_url = eDict['imsServiceUrl'] + self.kms_user = eDict['kmsUser'] + self.bucket = eDict['workspace'] + self.ims_public_key_bytes = base64.b64decode(eDict['imsPublicKeyBytes']) + + def dict(self): + return {'kmsServiceUrl': self.kms_url, + 'privateKeyBytes': binascii.b2a_base64(self.private_key_bytes), + 'sasServiceUrl': self.sas_url, + 'imsUser': self.ims_user, + 'storageProviders': self.providers, + 'publicKeyBytes': binascii.b2a_base64(self.public_key_bytes), + 'imsServiceUrl': self.ims_url, + 'kmsUser': self.kms_user, + 'workspace': self.bucket, + 'imsPublicKeyBytes': binascii.b2a_base64(self.ims_public_key_bytes)} + + def __repr__(self): + return json.dumps(self.dict(), sort_keys=True, indent=4) + + +class UserPublicCertificate(object): + username = None + certificate = None + + def __init__(self, props=None, username="", certificate=None): + if props: + self.username = props['username'] + self.certificate = props['key'] + else: + self.username = username + self.certificate = certificate + + def dict(self): + return { + 'username': self.username, + 'key': self.certificate + } + + def __repr__(self): + return json.dumps(self.dict(), sort_keys=True, indent=4) + +class UserPrivateKey(UserPublicCertificate): + pass + + +class Config(object): + def __init__(self, ims, kms, key, secret): + self.kmsUrl = kms + self.imsUrl = ims + self.client_key = key + self.client_secret = secret + + +class TrustStoreClientAuthenticationException(Exception): + def __init__(self, message, isConfigured=False): + self.message = message + self.isConfigured = isConfigured + + def __str__(self): + msg = repr(self.message) + " note: " + if self.isConfigured: + msg += " login attempt was made." + else: + msg += " not currently authenticated" + return msg + + +class OpenSSLVersionException(Exception): + def __init__(self, version): + self.version = version + + def __str__(self): + return "The version of openssl (" + self.version + ") is too old!"