comparison PythonTrustStore-0.2.0/py_ts/TrustStoreClient.py @ 1:ff126718bdc5

Uploaded
author cathywise
date Wed, 11 Dec 2013 21:05:12 -0500
parents
children
comparison
equal deleted inserted replaced
0:22cc897d85c5 1:ff126718bdc5
1 import requests
2 from requests_oauth2 import OAuth2
3 import simplejson as json
4 import simplejson.scanner
5 from urllib import quote
6 from urlparse import parse_qs, urljoin
7 from functools import wraps
8 from tempfile import NamedTemporaryFile
9 # from subprocess import call, check_output, Popen
10 import subprocess
11 import os
12 from urlparse import urlparse
13 from datetime import datetime
14 import xmltodict
15 import uuid
16 import webbrowser
17 import base64
18 import binascii
19 import hashlib
20 import textwrap
21 import re
22 import boto.exception
23 import boto.s3.connection as botoConn
24 from boto.s3.key import Key as botoKey
25 from distutils.version import LooseVersion
26 from parts import *
27 # import threading
28 from multiprocessing.pool import ThreadPool
29 # import Queue
30 from passlib.hash import pbkdf2_sha256
31 from passlib.utils import ab64_decode
32 from itertools import izip_longest
33 import sys
34
35 # import cProfile #, pstats
36
37
38 DASHES = "-----"
39 BEGIN = "BEGIN"
40 END = "END"
41 PUBLIC = "PUBLIC"
42 PRIVATE = "PRIVATE"
43 KEY = "KEY"
44 CERTIFICATE = "CERTIFICATE"
45
46 class TrustStoreClient(object):
47 """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.
48
49 """
50 brewOpenSSL = '/usr/local/Cellar/openssl/'
51 jsonHeaders = {'Content-type': 'application/json', 'Accept': 'application/json'}
52 storesPrefix = "/store"
53 storesList = "/all"
54 filesPrefix = "/file"
55 keysPrefix = "/keys"
56 publicKeysPrefix = "/public_key"
57 privateKeysPrefix = "/private_key"
58 usernamesPrefix = "/username"
59 saltPrefix = "/loginsalt"
60 passwordResetPrefix = "/credentials"
61
62 providers = []
63
64 pieceSize = 209715
65
66 openSSL = "openssl"
67
68 kmsUrl = u'http://localhost:8080/TSSKeyManagementService-Collaboration'
69 imsUrl = u'http://localhost:8080/TSSIntegrityManagementService/services/IMS'
70 client_key = u'my-trusted-client-with-secret'
71 client_secret = u'somesecret'
72 headless = False
73 redirect_uri = "oob"
74 keyFile = None
75 auth = None
76 kmsClient = None
77 username = None
78
79 def requiresAuth(func):
80 """Wraps a function which requires the client be already sucesfully authenticated with the Key Management Service."""
81 @wraps(func)
82 def withAuth(self, *args, **kwargs):
83 if self.kmsClient:
84 return func(self, *args, **kwargs)
85 else:
86 raise TrustStoreClientAuthenticationException("Not authenticated!")
87 return withAuth
88
89 def __init__(self, headless, config=None, accessToken=None):
90 """Set up the client, checking OpenSSL
91
92 :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
93 :type headless: boolean
94 :param config: Optional collection of configuration variables, must have values kmsUrl, imsUrl, client_key and client_secret.
95 :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.
96
97 """
98 if os.path.exists(self.brewOpenSSL):
99 versions = os.listdir(self.brewOpenSSL)
100 versions.sort(key=LooseVersion)
101 self.openSSL = self.brewOpenSSL + versions[-1] + "/bin/openssl"
102
103 sslVersion = subprocess.check_output([self.openSSL, 'version'])
104 m = re.search('([0-9]+.[0-9]+.[0-9]+[a-z]*)', sslVersion)
105 if LooseVersion(m.group(0)) < LooseVersion("1.0.0"):
106 raise OpenSSLVersionException(m.group(0))
107 elif not headless:
108 print "Working with " + sslVersion
109
110 if config:
111 self.kmsUrl = config.kmsUrl
112 self.imsUrl = config.imsUrl
113 self.client_key = config.client_key
114 self.client_secret = config.client_secret
115 self.headless = headless
116 self.auth = OAuth2(self.client_key, self.client_secret, self.kmsUrl, self.redirect_uri)
117 if accessToken:
118 self.kmsClient = requests.session(params={'access_token': accessToken})
119 self.username = self._getUsername()
120
121 def authenticate(self, username=None, password=None):
122 """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.
123
124 :param username: The username already registered with KMS.
125 :type username: string/unicode
126 :param password: The user's KMS password.
127 :type password: string/unicode
128 :rtype: None or string (authorisation url)
129
130 """
131
132 if not self.kmsClient:
133 response = None
134 if not username or not password:
135 authorization_url = self.auth.authorize_url(scope='read trust write', response_type='code')
136 if authorization_url:
137 if self.headless:
138 return authorization_url
139 else:
140 webbrowser.open(authorization_url)
141 code = raw_input('Code:')
142 response = self.auth.get_token(code, grant_type='authorization_code')
143 else:
144 self.username = username
145 self.password = password
146 ans = requests.get("%s%s/%s" %(self.kmsUrl, self.saltPrefix, username))
147 salt = base64.b64decode(ans.text)
148 # Passlib uses a *custom* base64 encoding because it is arse, so fix to normal.
149 sendPassword = base64.b64encode(ab64_decode(pbkdf2_sha256.encrypt(username + password, rounds=1000, salt=salt).split("$")[-1]))
150 login_form = {'username': username,
151 'password': sendPassword,
152 'client_id': self.client_key,
153 'client_secret': self.client_secret,
154 'grant_type': 'password'}
155 response = requests.post("%s%s" % (self.auth.site, quote(self.auth.token_url)), data=login_form, allow_redirects=True)
156
157 if isinstance(response.content, basestring):
158 try:
159 response = json.loads(response.content)
160 except ValueError:
161 response = parse_qs(response.content)
162 else:
163 response = response.content
164
165 if response:
166 try:
167 self.kmsClient = requests.session(params={'access_token': response['access_token']})
168 if not self.username:
169 self.username = self._getUsername()
170 except KeyError:
171 raise TrustStoreClientAuthenticationException("Credentials not accepted. Response: " + str(response))
172 else:
173 raise TrustStoreClientAuthenticationException("Communication error with TrustStore server. Is it running?")
174
175 def twoStageAuth(self, code):
176 """If using oAuth2, this method will need to be called after :func:`authenticate`
177
178 :param code: The oAuth2 token.
179 """
180
181 response = self.auth.get_token(code)
182 self.kmsClient = requests.session(params={'access_token': response['access_token']})
183 self.username = self._getUsername()
184
185 def clearAuth(self):
186 """Clear any authentication (username/password) but preserve other configuration (server locations etc)"""
187 self.auth = None
188 self.kmsClient = None
189 self.username = None
190
191 def addProvider(self, prov):
192 """Tell the client about a new provider to use (in addition)
193
194 :param prov: The new provider
195 :type prov: `Provider`
196
197 """
198 self.providers.append(prov)
199
200 @requiresAuth
201 def listStores(self):
202 """List all the stores this user has access to
203
204 :rtype: list of Store objects
205
206 """
207 stores = []
208 r = self.kmsClient.get(self.kmsUrl + self.storesPrefix) # + self.storesList)
209 if 'error' not in r.json:
210 for store in r.json:
211 stores.append(Store(store))
212 return stores
213 else:
214 print r.text
215 print r.json
216
217 @requiresAuth
218 def getPrivateKey(self, filename):
219 """Fetch the user's private key from KMS."""
220 getr = self.kmsClient.get(self.kmsUrl + self.privateKeysPrefix + "/" + self.username)
221 if getr.json and 'error' not in getr.json:
222 key = UserPrivateKey(getr.json)
223 if key.certificate:
224 with open(filename, 'w+') as f:
225 f.write(key.certificate)
226 self.keyFile = filename
227 else:
228 self.setNewKey(filename)
229 return filename
230 else:
231 print "Couldn't find private key?"
232 print getr.text
233
234 @requiresAuth
235 def setNewKey(self, filename):
236 """Generate a new private key, either because the user doesn't have one yet, or they've requested a new one."""
237 print "No private key found!"
238 self._generateKeypair(filename)
239 keydata = None
240 if filename:
241 with open(filename, 'r') as f:
242 keydata = f.read()
243 key = UserPrivateKey(None, self.username, keydata)
244 postr = self.kmsClient.post(self.kmsUrl + self.privateKeysPrefix, data=json.dumps(key.dict()), headers=self.jsonHeaders)
245 if postr.status_code != requests.codes.ok:
246 print "Server refused to save new private key."
247 return
248 return filename
249
250 @requiresAuth
251 def getStore(self, name):
252 """Get the first store (that the user has access to) matching the specified name."""
253 for store in self.listStores():
254 if store.friendly_name == name:
255 return store
256
257 @requiresAuth
258 def createStore(self, store=None, name=None):
259 """Create a new store.
260
261 :param store: Pre-prepared store to save to KMS.
262 :type store: Store
263 :param name: If no pre-prepared store, create new store with this name. If not supplied, store name will be "default".
264 :type name: string/unicode
265 :rtype: Store
266
267 """
268 if not store:
269 if not name:
270 name = "default"
271 store = Store(owner=self._getUsername(), friendly_name=name)
272 store.administrators.append(self._getUsername())
273
274 storejson = json.dumps(store.dict())
275 # print storejson
276 postr = self.kmsClient.post(self.kmsUrl + self.storesPrefix, data=storejson, headers=self.jsonHeaders)
277 if postr.status_code != requests.codes.ok:
278 print "Server refused request to save store description."
279 print postr.status_code
280 print postr.text
281 return
282
283 store = Store(postr.json)
284 storeFile = StoreProperties()
285 username = str(uuid.uuid4())
286 password = binascii.b2a_hex(os.urandom(20))
287 storeFile.ims_url = self.imsUrl
288 storeFile.kms_url = self.kmsUrl
289 storeFile.ims_user = {"ident": username, "secret": password}
290
291 storeFile.providers = self.providers
292 self._createNewBucket(storeFile)
293 self._createAndStoreEmptyDirectory(store, storeFile)
294
295 privateKeyFile = self._generatePKCS1Keypair()
296 if privateKeyFile:
297 publicKeyFile = self.__publicKeyFromPrivate(privateKeyFile)
298 storeFile.private_key_bytes = self.__readPrivateKeyFromFile(privateKeyFile)
299 storeFile.public_key_bytes = self.__readPublicKeyFromFile(publicKeyFile)
300 # print storeFile.private_key_bytes
301 # print storeFile.public_key_bytes
302 imsPublicKey = self._imsRegister(username, password, self.__readPublicKeyFromFile(publicKeyFile))
303 storeFile.ims_public_key_bytes = imsPublicKey
304 # print imsPublicKey
305 _json = postr.json
306 if self._putStoreFile(store, storeFile):
307 putr = self.kmsClient.put(self.kmsUrl + self.storesPrefix, data=json.dumps(store.dict()), headers=self.jsonHeaders)
308 if putr.status_code != requests.codes.ok:
309 print "Sever refused request to save store file."
310 print putr.status_code
311 print putr.text
312 else:
313 _json = putr.json
314 self.__remove(publicKeyFile)
315 self.__remove(privateKeyFile)
316 return Store(_json)
317
318 @requiresAuth
319 def updateStore(self, store):
320 """Update a store (where root file or permissions have changed, pressumably)."""
321 putr = self.kmsClient.put(self.kmsUrl + self.storesPrefix, data=json.dumps(store.dict()), headers=self.jsonHeaders)
322 if putr.status_code != requests.codes.ok:
323 print putr.status_code
324 print putr.text
325 else:
326 # update access
327 storeFile = self._getStoreFile(store)
328 self._putStoreFile(store, storeFile)
329 return Store(putr.json)
330
331 @requiresAuth
332 def deleteStore(self, store):
333 """Delete a store (with the same id as this store, anyway). This is not recoverable!!"""
334 url = self.kmsUrl + self.storesPrefix + "/" + str(store.id)
335 delr = self.kmsClient.delete(url)
336 if delr.status_code != requests.codes.ok:
337 print delr.status_code
338 print url
339 print delr.text
340
341 @requiresAuth
342 def changePassword(self, newPassword):
343 """Change the user's password to a new one. This requires the old password be known!"""
344 newKeyFile = self._changeKeyPassword(self.password, newPassword)
345 key = None
346 with open(newKeyFile, 'rb') as f:
347 key = f.read()
348 ans = requests.get("%s%s/%s" %(self.kmsUrl, self.saltPrefix, self.username))
349 salt = base64.b64decode(ans.text)
350 sendNewPassword = base64.b64encode(ab64_decode(pbkdf2_sha256.encrypt(self.username + newPassword, rounds=1000, salt=salt).split("$")[-1]))
351 sendOldPassword = base64.b64encode(ab64_decode(pbkdf2_sha256.encrypt(self.username + self.password, rounds=1000, salt=salt).split("$")[-1]))
352 reset = {"password":sendNewPassword, "oldPassword":sendOldPassword, "key":{"username":self.username, "key": key}}
353 postr = self.kmsClient.post(self.kmsUrl + self.passwordResetPrefix, data=json.dumps(reset), headers=self.jsonHeaders)
354 if postr.status_code != requests.codes.ok:
355 print postr.status_code
356 print postr.text
357
358 @requiresAuth
359 def listDirectory(self, store):
360 """List all the files in the given store.
361
362 :rtype: Directory
363
364 """
365 directory = None
366 makeDirectory = False
367 storeFile = self._getStoreFile(store)
368 if storeFile and storeFile.bucket:
369 provider = self._getCloudService(storeFile)
370 tmpRoot = self._getPartFromCloud(store.index_codename, provider, storeFile.bucket)
371 if tmpRoot:
372 key = self._getKeyForFragment(store.index_codename, store.id)
373 if key:
374 tmpRoot = self._decryptPart(tmpRoot, key, store.iv)
375 if tmpRoot:
376 jsonText = ""
377 with open(tmpRoot) as f:
378 jsonText = f.read()
379 directory = Directory(jsonText=jsonText)
380 self.__remove(tmpRoot)
381 else:
382 makeDirectory = True
383 else:
384 makeDirectory = True
385 self.__remove(tmpRoot)
386 else:
387 makeDirectory = True
388 elif storeFile and len(storeFile.providers) > 0:
389 self._createNewBucket(storeFile)
390 makeDirectory = True
391 else:
392 print "has no bucket"
393 print storeFile
394
395 if makeDirectory:
396 print "creating empty directory"
397 directory = self._createAndStoreEmptyDirectory(store, storeFile)
398 self.updateStore(store)
399 return directory
400
401 @requiresAuth
402 def updateDirectory(self, directory, store):
403 # Check for conflicts!!
404 newName = unicode(uuid.uuid4())
405 storeFile = self._getStoreFile(store)
406 provider = self._getCloudService(storeFile)
407 tmpText = json.dumps(directory.dict())
408 tmpRoot = self.__temporaryFileWithBytes(tmpText)
409 key = self._generateKey()
410 store.iv = self._generateIV()
411 tmpRoot = self._encryptPart(tmpRoot, key, store.iv)
412 self._putPartInCloud(newName, provider, storeFile.bucket, tmpRoot)
413 self._setKeyForFragment(newName, key, store.id)
414 store.index_codename = newName
415 self.updateStore(store)
416 self.__remove(tmpRoot)
417 pass
418
419 @requiresAuth
420 def getFile(self, store, files, threads=10):
421 storeFile = self._getStoreFile(store)
422 provider = self._getCloudService(storeFile)
423 if files:
424 keySets = self._getKeysForFragments(files.fragments, store.id)
425 if keySets:
426 clearFile = self.__temporaryFile()
427 with open(clearFile, 'r+b') as f:
428 junk = b'\x00' * (files.remote_size)
429 f.write(junk)
430 # with open(clearFile, 'wb') as f:
431 promises = [];
432 pool = ThreadPool(processes=threads)
433 for fragmentName in keySets:
434 fragment = None
435 for frag in files.fragments:
436 if frag.name == fragmentName:
437 fragment = frag
438
439 promise = pool.apply_async(self._doFragmentDownload, [clearFile, fragmentName, provider, storeFile, keySets, fragment])
440 promises.append(promise)
441 for order, promise in enumerate(promises):
442 self.__graphPrinter(order, len(promises))
443 success = promise.get()
444 self.__graphPrinter(order + 1, len(promises))
445
446 if not success:
447 print "Error on part!"
448 break
449 files.local_path = clearFile
450 sys.stdout.write("\n")
451 sys.stdout.flush()
452 return clearFile
453
454 def __graphPrinter(self, done, total):
455 completed = int(((done) * 100.0) / total)
456 sys.stdout.write(" |" + "=" * completed + "-" * (100 - completed) + "| \r")
457 sys.stdout.flush()
458
459 @requiresAuth
460 def getBytes(self, start, end, store, files):
461 storeFile = self._getStoreFile(store)
462 provider = self._getCloudService(storeFile)
463 fragments = files.fragments
464 allData = None
465 sizeSoFar = 0
466 for fragment, idx in enumerate(fragments):
467 if sizeSoFar + fragment.length >= start and sizeSoFar <= end:
468 # Get this fragment
469 key = self._getKeyForFragment(fragment, store.id)
470 isOkay = False
471 attempts = 0
472 while not isOkay and attempts < 10:
473 tmpPart = self._getPartFromCloud(fragment.name, provider, storeFile.bucket)
474 if tmpPart:
475 isOkay = self._getVerifyPart(tmpPart, fragment.name, storeFile)
476 attempts += 1
477 else:
478 break
479 if isOkay:
480 tmpPart2 = self._decryptPart(tmpPart, key, fragment.iv)
481 self.__remove(tmpPart)
482 if tmpPart2:
483 with open(tmpPart2, 'rb') as t:
484 if sizeSoFar <= start:
485 t.seek(start - sizeSoFar)
486 thisMuch = fragment.length
487 if sizeSoFar + fragment.length < end:
488 thisMuch = (sizeSoFar + fragment.length) - end
489 allData += t.read(thisMuch)
490 self.__remove(tmpPart2)
491 else:
492 print "File corrupt."
493 break
494
495 sizeSoFar += fragment.length
496 return allData
497
498 @requiresAuth
499 def updateFile(self, store, file_, path, directory):
500 """
501 Update a file on TrustStore.
502
503 :type store: Store
504 :param store: a store to upload the file to
505
506 :type file_: File
507 :param file_: the File object to upload. Must have local_path
508
509 :type path: Directory
510 :param path: the child folder to upload the File to. May be same as directory
511
512 :type directory: Directory
513 :param directory: root folder
514 """
515 # pr = cProfile.Profile()
516 # pr.enable()
517 print file_.name
518 if file_.remote_size <= 0:
519 file_.remote_size = os.path.getsize(file_.local_path)
520 if store and file_ and path and directory:
521 storeFile = self._getStoreFile(store)
522 if storeFile:
523 provider = self._getCloudService(storeFile)
524 fileSize = os.path.getsize(file_.local_path)
525 readFragments = 0
526 displacement = 0
527 optimalSize = self.__optimalPieceSize(fileSize)
528 promises = [];
529 pool = ThreadPool(processes=10)
530 if len(file_.fragments) > 0:
531 self._deleteKeysForFragments(file_.fragments, store.id)
532 count404 = 0
533 for fragment in file_.fragments:
534 if self._deletePartFromCloud(fragment.name, provider, storeFile.bucket) == "404":
535 count404 += 1
536 sys.stdout.write(" Trying to delete old file. Parts not found in cloud: " + str(count404) + " of " + str(len(file_.fragments)) + "\r")
537 sys.stdout.flush()
538 file_.fragments = []
539 while fileSize > readFragments * optimalSize:
540 sys.stdout.write(" " + str(readFragments) + " optimal: " + str(optimalSize) + " file size: " + str(fileSize) + "\r")
541 sys.stdout.flush()
542 promise = pool.apply_async(self._doFragmentUpload, [file_.local_path, optimalSize, readFragments, displacement, storeFile, store.id, provider])
543 readFragments += 1
544 # ) #
545 displacement += optimalSize
546 promises.append(promise)
547 for order, promise in enumerate(promises):
548 self.__graphPrinter(order, len(promises))
549 part = promise.get()
550 if part:
551 file_.fragments.append(part)
552 else:
553 print "Part broken?"
554 break
555 self.__graphPrinter(order + 1, len(promises))
556 isUpdate = False
557 for child in path.children:
558 if file_.name == child.name:
559 # Assume update.
560 child = file_
561 isUpdate = True
562 if not isUpdate:
563 path.children.append(file_)
564 # print directory
565 self.updateDirectory(directory, store)
566 sys.stdout.write("\n")
567 sys.stdout.flush()
568 else:
569 print "Hey, you can't upload that! : " + file_.local_path
570 # pr.disable()
571 # pr.print_stats(1)
572
573 @requiresAuth
574 def delFile(self, store, fileName, path, directory):
575 if store and fileName and path and directory:
576 for child in path.children:
577 print child.name
578 path.children[:] = [child for child in path.children if self._deleteChildren(fileName, child, store)]
579 self.updateDirectory(directory, store)
580 elif store and path and directory:
581 for child in path.children:
582 print child.name
583 self._deleteChildren(None, child, store)
584 path.children = []
585 self.updateDirectory(direcroty, store)
586 else:
587 print "Not enough information to delete!"
588
589 @requiresAuth
590 def addFile(self, store, file_, path, directory):
591 if store and file_ and path and directory and os.path.exists(file_.local_path):
592 self.updateFile(store, file_, path, directory)
593 else:
594 print "Hey, you can't upload that! " + file_.local_path
595
596 ## Past here should no user go.
597
598 def _createAndStoreEmptyDirectory(self, store, storeFile):
599 directory = Directory()
600 newName = unicode(uuid.uuid4())
601 store.iv = self._generateIV()
602 provider = self._getCloudService(storeFile)
603 tmpText = json.dumps(directory.dict())
604 tmpRoot = self.__temporaryFileWithString(tmpText)
605 key = self._generateKey()
606 tmpRoot = self._encryptPart(tmpRoot, key, store.iv)
607 self._putPartInCloud(newName, provider, storeFile.bucket, tmpRoot)
608 self._setKeyForFragment(newName, key, store.id)
609 store.index_codename = newName
610 self.__remove(tmpRoot)
611 return directory
612
613 def _createNewBucket(self, storeFile):
614 storeFile.bucket = "truststore-" + str(uuid.uuid4())
615 provider = self._getCloudService(storeFile)
616 provider.create_bucket(storeFile.bucket)
617
618
619 def _deleteChildren(self, fileName, child, store):
620 if fileName == child.name or fileName == None:
621 storeFile = self._getStoreFile(store)
622 if storeFile:
623 try:
624 self._deleteKeysForFragments(child.fragments, store.id)
625 provider = self._getCloudService(storeFile)
626 count404 = 0
627 for fragment in child.fragments:
628 if self._deletePartFromCloud(fragment.name, provider, storeFile.bucket) == "404":
629 count404 += 1
630 sys.stdout.write(" Trying to delete old file. Parts not found in cloud: " + str(count404) + " of " + str(len(child.fragments)) + "\r")
631 sys.stdout.flush()
632 child.fragments = []
633 print "\n"
634 print "Deleted file " + fileName
635 except AttributeError:
636 for subs in child.children:
637 print subs.name
638 self._deleteChildren(None, subs, store)
639 return False
640 else:
641 return True
642
643 def _doFragmentDownload(self, filename, fragmentName, provider, storeFile, keySets, fragment):
644 isOkay = False
645 tries = 0
646 sucess = False
647 tmpPart = None
648 while not isOkay and tries < 10:
649 tmpPart = self._getPartFromCloud(fragmentName, provider, storeFile.bucket)
650 if tmpPart:
651 isOkay = self._getVerifyPart(tmpPart, fragmentName, storeFile)
652 tries += 1
653 else:
654 break
655 if isOkay:
656 tmpPart2 = self._decryptPart(tmpPart, keySets[fragmentName], fragment.iv)
657 if tmpPart2:
658 with open(tmpPart2, 'rb') as t:
659 currentSize = os.path.getsize(filename)
660 data = t.read()
661 if currentSize <= fragment.offset:
662 with open(filename, 'a+b') as f:
663 if currentSize < fragment.offset:
664 sys.stdout.write(" !! :(")
665 junk = b'\x00' * (fragment.offset - currentSize)
666 f.write(junk)
667 f.write(data)
668 sys.stdout.write(" Piece " + str(fragment.order) + " \t\t\r")
669 sys.stdout.flush()
670 sucess = True
671 else:
672 with open(filename, 'r+b') as f:
673 f.seek(fragment.offset, 0)
674 sys.stdout.write(" Piece " + str(fragment.order) + " \t\t\r") # + ": " + str(f.tell()) + ": " + str(len(data)) + "\r")
675 sys.stdout.flush()
676 f.write(data)
677 sucess = True
678 self.__remove(tmpPart2)
679 else:
680 print "Part broken!!"
681 self.__remove(tmpPart)
682 return sucess
683
684 def _doFragmentUpload(self, localPath, optimalSize, readFragments, displacement, storeFile, storeId, provider):
685 fragment = Fragment()
686 with open(localPath, 'rb') as f:
687 fragment.length = optimalSize
688 fragment.order = readFragments
689 fragment.providers = storeFile.providers
690 fragment.offset = displacement
691 f.seek(displacement)
692 data = f.read(fragment.length)
693 # sys.stdout.write(" " + str(fragment.order) + " displacement: " + str(displacement) + " length: " + str(fragment.length)
694 # + "\r")
695 # sys.stdout.flush()
696 if len(data) < fragment.length:
697 fragment.length = len(data)
698 # sys.stdout.write(" " + str(fragment.order) + " true length: " + str(len(data)) + " \r")
699 sys.stdout.flush()
700
701 tmpDataFile = self.__temporaryFileWithBytes(data)
702 key = self._generateKey()
703 fragment.name = unicode(uuid.uuid4())
704 fragment.iv = self._generateIV()
705 encryptedPart = self._encryptPart(tmpDataFile, key, fragment.iv)
706 self._putPartInCloud(fragment.name, provider, storeFile.bucket, encryptedPart)
707 self._setKeyForFragment(fragment.name, key, storeId)
708 self._storeVerifyPart(encryptedPart, fragment.name, storeFile)
709 # sys.stdout.write(" Finished fragment " + unicode(readFragments) + " \r")
710 sys.stdout.flush()
711 self.__remove(tmpDataFile)
712 self.__remove(encryptedPart)
713 return fragment
714
715 def _getStoreFile(self, store):
716 getr = self.kmsClient.get(self.kmsUrl + self.filesPrefix + "/" + unicode(store.id)) # + "/" + unicode(store.filename))
717 message = getr.content
718 if message:
719 clear = self._decryptStoreFileUsingPrivateKeyfile(message, self.keyFile)
720 if clear:
721 # print clear
722 try:
723 storefile = json.loads(clear)
724 storeProps = StoreProperties(eDict=storefile)
725 # print json.dumps(storeProps.dict())
726 return storeProps
727 except simplejson.scanner.JSONDecodeError:
728 print "This profile is probably XML."
729 else:
730 print "Failed to decrypt profile, download status code: " + str(getr.status_code)
731
732 def _putStoreFile(self, store, storeFile):
733 success = False
734 userList = []
735 userList += store.readers
736 userList += store.writers
737 userList += store.administrators
738 userList.append(store.owner)
739 userList = list(set(userList))
740 storeDict = storeFile.dict()
741 messageFile = self._encryptStoreFileForUsers(json.dumps(storeDict), userList)
742 # print json.dumps(storeDict)
743 if messageFile:
744 with open(messageFile, 'rb') as f:
745 message = {'filename': ("file", f)} # this filename "file" is never read but it needs to be there to be valid so it's just whatever.
746 url = self.kmsUrl + self.filesPrefix + "/" + unicode(store.id)
747 postr = self.kmsClient.post(url, files=message)
748 # print postr.request.headers
749 # print postr.request.data
750 self.__remove(messageFile)
751 if postr.status_code == requests.codes.ok:
752 success = True
753 else:
754 print postr.status_code
755 print postr.text
756 print "Failed to put Store File"
757 print url
758 print store.id
759 return success
760
761 def _getCloudService(self, storeFile):
762 for provider in storeFile.providers:
763 api = provider['api']
764 if api == "nectar" or api == "s3":
765 calling = botoConn.SubdomainCallingFormat()
766 if api == "nectar":
767 calling = botoConn.OrdinaryCallingFormat()
768 user = provider['userCredentials']
769 endpoint = provider['endpoint']
770 connection = botoConn.S3Connection(
771 aws_access_key_id=user['ident'],
772 aws_secret_access_key=user['secret'],
773 port=urlparse(endpoint).port,
774 host=urlparse(endpoint).hostname,
775 is_secure=True,
776 validate_certs=False,
777 calling_format=calling
778 )
779 return connection
780
781 def _changeKeyPassword(self, old, new):
782 newKey = self.keyFile + ".new"
783 openssl = [self.openSSL, 'rsa', '-aes128', '-in', self.keyFile, '-out', newKey, '-passin', 'pass:' + old, '-passout', 'pass:' + new]
784 okay = subprocess.call(openssl)
785 if (okay == 0):
786 self.keyFile = newKey
787 return newKey
788 else:
789 return False
790
791 def _getPublicCertFor(self, username):
792 getr = self.kmsClient.get(self.kmsUrl + self.publicKeysPrefix + "/" + username)
793 return UserPublicCertificate(getr.json)
794
795 def _setPublicCert(self, username):
796 publicCertFile = self.__makeRSACertificateFromPrivate(self.keyFile)
797 if publicCertFile:
798 certificate = self.__readCertificateFromFile(publicCertFile)
799 cert = UserPublicCertificate(None, username, certificate)
800 headers = {'Content-type': 'application/json', 'Accept': 'application/json'}
801 self.kmsClient.post(self.kmsUrl + self.publicKeysPrefix, data=json.dumps(cert.dict()), headers=headers)
802 self.__remove(publicCertFile)
803
804 def _checkPublicCert(self, cert):
805 print "Checking your certificate..."
806 certFile = self.__writeCertificateToFile(cert.certificate)
807 openssl = [self.openSSL, 'x509', '-in', certFile, '-modulus', '-noout']
808 # certModulus = subprocess.check_output(openssl)
809 p = subprocess.Popen(openssl, stdout=subprocess.PIPE)
810 certModulus = p.communicate()[0]
811 openssl = [self.openSSL, 'rsa', '-in', self.keyFile, '-modulus', '-noout']
812 if self.password:
813 openssl.append("-passin")
814 openssl.append("pass:" + self.password)
815
816 keyModulus = subprocess.check_output(openssl)
817
818 if (certModulus == keyModulus):
819 self.__remove(certFile)
820 return True
821 else:
822 print certFile
823 print certModulus
824 print keyModulus
825 return False
826
827
828 def _getKeyForFragment(self, fragmentName, storeID):
829 getr = self.kmsClient.get(self.kmsUrl + self.keysPrefix + "/" + unicode(storeID), params={'codenames': fragmentName})
830 if getr.status_code != requests.codes.ok or not getr.json:
831 print getr.status_code
832 print "Failed to get key for fragment"
833 if not getr.json:
834 print "No key for that fragment known."
835 return
836 return base64.b64decode(getr.json[0]['key'])
837
838 def _getKeysForFragments(self, fragments, storeID):
839 codenames = [fragment.name for fragment in fragments]
840 args = [iter(codenames)] * 20
841 batches = izip_longest(fillvalue=None, *args)
842 sets = {}
843 for batch in batches:
844 getr = self.kmsClient.get(self.kmsUrl + self.keysPrefix + "/" + unicode(storeID), params={'codenames': batch})
845 if getr.json:
846 sets.update(dict([(key['codename'], base64.b64decode(key['key'])) for key in getr.json]))
847 elif getr.status_code != requests.codes.ok:
848 print "Failed to get keys for " + str(len(batch)) + " fragments."
849 print getr
850 print getr.text
851 return sets
852
853 def _setKeysForFragments(self, storeID, keySets):
854 data = json.dumps([{'codename': fragmentName, 'key': base64.b64encode(keySets[fragmentName]), 'expiryDate': None} for fragmentName in keySets])
855 headers = {'Content-type': 'application/json', 'Accept': 'application/json'}
856 postr = self.kmsClient.post(self.kmsUrl + self.keysPrefix + "/" + unicode(storeID), data=data, headers=headers)
857 if postr.status_code != requests.codes.ok:
858 print postr.status_code
859 print "Failed to set keys for fragments"
860
861 def _deleteKeysForFragments(self, fragments, storeID):
862 codenames = [fragment.name for fragment in fragments]
863 url = self.kmsUrl + self.keysPrefix + "/" + unicode(storeID)
864 args = [iter(codenames)] * 20
865 batches = izip_longest(fillvalue=None, *args)
866 for batch in batches:
867 try:
868 delr = self.kmsClient.delete(url, params={'codenames': batch})
869 if delr.status_code != requests.codes.ok:
870 print delr.status_code
871 print delr.text
872 print delr.content
873 print "Failed to delete keys."
874 except requets.exceptions.ConnectionError as e:
875 print (e)
876 print url
877 print batch
878
879
880 def _setKeyForFragment(self, fragmentName, key, storeID):
881 data = json.dumps([{'codename': fragmentName, 'key': base64.b64encode(key), 'expiryDate': None}])
882 headers = {'Content-type': 'application/json', 'Accept': 'application/json'}
883 postr = self.kmsClient.post(self.kmsUrl + self.keysPrefix + "/" + unicode(storeID), data=data, headers=headers)
884 if postr.status_code != requests.codes.ok:
885 print postr.status_code
886 print postr.text
887 print "Failed to set key for fragment"
888
889 def _decryptStoreFileUsingPrivateKeyfile(self, message, keyFile):
890
891 publicCertificate = self._getPublicCertFor(self.username).certificate
892
893 clearMessage = ""
894 messageFileName = self.__temporaryFileWithBytes(message)
895 decryptedFileName = self.__temporaryFile()
896 certFileName = self.__writeCertificateToFile(publicCertificate)
897
898 openssl = [self.openSSL, 'cms', '-decrypt',
899 '-in', messageFileName,
900 '-out', decryptedFileName,
901 '-recip', certFileName,
902 '-inkey', keyFile,
903 '-inform', 'DER']
904
905 if self.password:
906 openssl.append("-passin")
907 openssl.append("pass:" + self.password)
908
909 # decrypt
910 # openssl smime -decrypt -in encrypted -out decrypted -recip public_cert -inkey private_key
911 try:
912 okay = subprocess.call(openssl)
913
914 if (okay == 0):
915 with open(decryptedFileName) as decryptFile:
916 clearMessage = decryptFile.read()
917 else:
918 print okay
919 print "Failed to decrypt store file "
920 # + messageFileName
921 except TypeError:
922 print "Failed to call OpenSSL properly:"
923 print openssl
924
925 # print certFileName
926 self.__remove(certFileName)
927 # print messageFileName
928 self.__remove(messageFileName)
929 self.__remove(decryptedFileName)
930
931 return clearMessage
932
933 def _encryptStoreFileForUsers(self, plain, userlist):
934 certificateList = [self._getPublicCertFor(username).certificate for username in userlist]
935 certificateFileList = [self.__writeCertificateToFile(cert) for cert in certificateList]
936 decryptedFileName = self.__temporaryFileWithString(plain)
937 encryptFileName = self.__temporaryFile()
938
939 argList = [self.openSSL, 'cms', '-encrypt', '-aes128',
940 '-in', decryptedFileName,
941 '-out', encryptFileName,
942 '-outform', 'DER']
943 argList += certificateFileList
944 # print argList
945 okay = subprocess.call(argList)
946
947 self.__remove(decryptedFileName)
948 for cert in certificateFileList:
949 self.__remove(cert)
950
951 if okay != 0:
952 print okay
953 print "Failed to encrypt store file"
954 print argList
955 return False
956 else:
957 for cert in certificateFileList:
958 self.__remove(cert)
959 return encryptFileName
960
961 def _getPartFromCloud(self, codename, provider, bucketName):
962 attempts = 0
963 while attempts < 20:
964 try:
965 bucket = provider.get_bucket(bucketName)
966 k = botoKey(bucket)
967 k.key = codename
968 tmpPart = self.__temporaryFile()
969 k.get_contents_to_filename(tmpPart)
970 return tmpPart
971 except (boto.exception.S3ResponseError, boto.exception.BotoServerError) as e:
972 if e.status == 403 or e.status == "403":
973 sys.stdout.write(" Error talking to " + str(provider) + ", attempt: " + str(attempts + 1) + " (403) \r")
974 else:
975 sys.stdout.write(" Error talking to " + str(provider) + ", attempt: " + str(attempts + 1) + ": " + str(e) + " \r")
976 sys.stdout.flush()
977 attempts += 1
978
979 def _putPartInCloud(self, codename, provider, bucketName, part):
980 attempts = 0
981 while attempts < 20:
982 try:
983 bucket = provider.get_bucket(bucketName)
984 k = botoKey(bucket)
985 k.key = codename
986 k.set_contents_from_filename(part)
987 return
988 except boto.exception.S3ResponseError:
989 sys.stdout.write(" Encountered a storing error talking to " + str(provider) + ", attempt: " + str(attempts + 1) + "\r")
990 sys.stdout.flush()
991 attempts += 1
992
993 def _deletePartFromCloud(self, codename, provider, bucketName):
994 attempts = 0
995 while attempts < 10:
996 try:
997 bucket = provider.get_bucket(bucketName)
998 k = botoKey(bucket)
999 k.key = codename
1000 bucket.delete_key(k)
1001 return
1002 except boto.exception.S3ResponseError as e:
1003 if "404" not in str(e):
1004 sys.stdout.write(" Encountered a delete error talking to " + str(provider) + ", attempt: " + str(attempts + 1) + "\r")
1005 sys.stdout.flush()
1006 attempts += 1
1007 else:
1008 return "404"
1009
1010 def _decryptPart(self, part, key, iv):
1011 decryptedPart = self.__temporaryFile()
1012 mode = '-aes-128-cbc'
1013 if not iv:
1014 mode = '-aes-128-ecb'
1015 iv = b'\x00'
1016 argList = [self.openSSL, 'enc', mode, '-d', '-in', part, '-out', decryptedPart, '-K', binascii.b2a_hex(key), '-iv', binascii.b2a_hex(iv)]
1017 okay = subprocess.call(argList)
1018 # print argList
1019
1020 if okay != 0:
1021 print okay
1022 print "Failed to decrypt part"
1023 print argList
1024 self.__remove(decryptedPart)
1025 return
1026 return decryptedPart
1027
1028 def _encryptPart(self, part, key, iv):
1029 encryptedPart = self.__temporaryFile()
1030 mode = '-aes-128-cbc'
1031 if not iv:
1032 mode = '-aes-128-ecb'
1033 iv = b'\x00'
1034 argList = [self.openSSL, 'enc', mode, '-in', part, '-out', encryptedPart, '-K', binascii.b2a_hex(key), '-iv', binascii.b2a_hex(iv)]
1035 okay = subprocess.call(argList)
1036 # print argList
1037
1038 if okay != 0:
1039 print okay
1040 print "Failed to encrypt part"
1041 print argList
1042 return encryptedPart
1043
1044 def _imsRegister(self, username, password, publicKey):
1045 url = urljoin(self.imsUrl, "rest/user/" + username)
1046 headers = {'passwd': password}
1047 # postr = requests.post(url, headers=headers, data=base64.b64encode(publicKey))
1048
1049 postr = requests.post(url, headers=headers, data={'passwd': password, 'publicKey': base64.b64encode(publicKey)})
1050 if postr.text and postr.status_code == 200:
1051 text = self.__cleanString(postr.text)
1052 try:
1053 # print "IMS Public Key"
1054 # print text
1055 return base64.b64decode(text)
1056 except (UnicodeError, TypeError):
1057 print "Error getting IMS Public certificate."
1058 return postr.content
1059 else:
1060 print unicode(postr.status_code) + " received while trying to register with IMS."
1061 print postr.text
1062
1063 def _getVerifyPart(self, part, partName, storeFile):
1064 isOkay = False
1065 url = urljoin(storeFile.ims_url, "rest/hash/" + storeFile.ims_user['ident'])
1066 headers = {'passwd': storeFile.ims_user['secret']}
1067 params = {"codename": partName}
1068 getr = requests.get(url, headers=headers, params=params)
1069 if getr.text and getr.json:
1070 # try:
1071 # resp = xmltodict.parse(getr.text)
1072 resp = getr.json
1073 # if "html" not in resp and "hashInfo" in resp:
1074 if "doubleSignature" in resp:
1075 # resp = resp["hashInfo"]
1076 keyFile = self.__writePublicKeyToFile(storeFile.public_key_bytes)
1077
1078 signature = base64.b64decode(resp["doubleSignature"])
1079 sigFile = self.__temporaryFileWithBytes(signature)
1080 meta = resp["signableMetaInfo"]
1081 plainfile = self.__temporaryFileWithString(meta)
1082
1083 argList = [self.openSSL, 'dgst', '-sha1', '-verify', keyFile, '-signature', sigFile, plainfile]
1084 okay = subprocess.check_output(argList)
1085 verified = (okay and okay == "Verified OK\n")
1086
1087 self.__remove(sigFile)
1088 self.__remove(keyFile)
1089 self.__remove(plainfile)
1090 if not verified:
1091 print "Unable to verify part."
1092 return isOkay
1093
1094 sums, imsSum = "", ""
1095 with open(part, 'rb') as f:
1096 sums = hashlib.md5(f.read()).hexdigest()
1097 imsSum = meta[:meta.find("$")]
1098 if sums == imsSum:
1099 isOkay = True
1100 else:
1101 print imsSum
1102 print sums
1103 else:
1104 print resp
1105 # except Exception as e:
1106 # print url
1107 # print getr.text
1108 # print e
1109 return isOkay
1110
1111 def _storeVerifyPart(self, part, partName, storeFile):
1112 isOkay = False
1113 headers = {'passwd': storeFile.ims_user['secret']}
1114 sums = ""
1115 with open(part, 'rb') as f:
1116 sums = hashlib.md5(f.read()).hexdigest()
1117
1118 imsKeyfile = self.__writePublicKeyToFile(storeFile.ims_public_key_bytes)
1119
1120 signable = sums + "$" + partName + "$" + datetime.utcnow().isoformat()
1121
1122 signableFile = self.__temporaryFileWithString(signable)
1123
1124 signable64 = base64.b64encode(signable)
1125 url = urljoin(storeFile.ims_url, "rest/sign/" + signable64)
1126 getr = requests.get(url, headers=headers)
1127 if getr.text: # and "string" in getr.text:
1128 resp = getr.text # xmltodict.parse(getr.text)
1129 resp = resp.replace("\\r\\n", "\n")
1130 signature = base64.b64decode(resp) #["string"])
1131 # print resp
1132 # print len(signature)
1133 # print len(storeFile.ims_public_key_bytes)
1134
1135 imsSignatureFile = self.__temporaryFileWithBytes(signature)
1136
1137 argList = [self.openSSL, 'dgst', '-sha1', '-verify', imsKeyfile, '-signature', imsSignatureFile, signableFile]
1138 okay = subprocess.check_output(argList)
1139 verified = (okay == "Verified OK\n")
1140 self.__remove(imsSignatureFile)
1141 if not verified:
1142 print "Unable to make digest."
1143 print argList
1144 else:
1145 nonce = os.urandom(4)
1146 nonceFile = self.__temporaryFileWithBytes(nonce)
1147 encryptedNonceFile = self.__temporaryFile()
1148 argList = [self.openSSL, 'rsautl', '-encrypt', '-pubin', '-inkey', imsKeyfile, '-in', nonceFile, '-out', encryptedNonceFile]
1149 okay = subprocess.call(argList)
1150 if okay != 0:
1151 print "Unable to encrypt test."
1152 print argList
1153 else:
1154 encryptedNonce = ""
1155 with open(encryptedNonceFile, 'rb') as f:
1156 encryptedNonce = f.read()
1157
1158 signatureFile = self.__temporaryFile()
1159 privKeyFile = self.__writePrivateKeyToFile(storeFile.private_key_bytes)
1160 argList = [self.openSSL, 'dgst', '-sha1', '-sign', privKeyFile, '-out', signatureFile, signableFile]
1161 okay = subprocess.call(argList)
1162 if okay == 0:
1163
1164 signature = ""
1165 with open(signatureFile, 'rb') as f:
1166 signature = f.read()
1167
1168 url = urljoin(storeFile.ims_url, "rest/check/" + storeFile.ims_user['ident'])
1169 params = {
1170 "nonce": base64.b64encode(encryptedNonce),
1171 "doubleSig": base64.b64encode(signature),
1172 "xml": signable
1173 }
1174 getr = requests.get(url, headers=headers, params=params)
1175 # print url
1176 # print headers
1177 # print params
1178 if getr.text: # and "string" in getr.text and xmltodict.parse(getr.text)["string"]:
1179 resp = self.__cleanString(getr.text)
1180 resp = base64.b64decode(resp) #xmltodict.parse(getr.text)["string"])
1181
1182 imsEncryptedNonceFile = self.__temporaryFileWithBytes(resp)
1183
1184 imsNonceFile = self.__temporaryFile()
1185 argList = [self.openSSL, 'rsautl', '-decrypt', '-inkey', privKeyFile, '-out', imsNonceFile, '-in', imsEncryptedNonceFile]
1186 okay = subprocess.call(argList)
1187 if okay != 0:
1188 return isOkay
1189
1190 imsNonce = ""
1191 with open(imsNonceFile, 'r') as f:
1192 imsNonce = f.read()
1193
1194 if imsNonce != nonce:
1195 return isOkay
1196
1197 url = urljoin(storeFile.ims_url, "rest/storehash/" + storeFile.ims_user['ident'])
1198 payload = {"xml": signable, "doublesign": base64.b64encode(signature), "codename": partName}
1199 postr = requests.post(url, headers=headers, params=params, data=payload)
1200 if postr.text and "OK" in postr.text: # and "string" in postr.text
1201 isOkay = True
1202 else:
1203 print postr.text
1204
1205 self.__remove(imsEncryptedNonceFile)
1206 self.__remove(imsNonceFile)
1207 else:
1208 print getr.text
1209 else:
1210 print "Unable to sign digest."
1211 self.__remove(signatureFile)
1212 self.__remove(privKeyFile)
1213 self.__remove(nonceFile)
1214 self.__remove(encryptedNonceFile)
1215 self.__remove(imsSignatureFile)
1216 else:
1217 print getr.text
1218 self.__remove(imsKeyfile)
1219 self.__remove(signableFile)
1220 return isOkay
1221
1222 def __temporaryFile(self):
1223 fileName = ""
1224 with NamedTemporaryFile(delete=False) as aFile:
1225 fileName = aFile.name
1226 return fileName
1227
1228 def __temporaryFileWithString(self, string):
1229 fileName = self.__temporaryFile()
1230 with open(fileName, 'w') as f:
1231 if string:
1232 f.write(string)
1233 return fileName
1234
1235 def __temporaryFileWithBytes(self, bytes):
1236 fileName = self.__temporaryFile()
1237 with open(fileName, 'wb') as f:
1238 if bytes:
1239 f.write(bytes)
1240 return fileName
1241
1242 def __writeCertificateToFile(self, cert):
1243 certFile = self.__temporaryFile()
1244 header = DASHES + BEGIN + " " + CERTIFICATE + DASHES
1245 footer = DASHES + END + " " + CERTIFICATE + DASHES
1246 with open(certFile, 'w') as f:
1247 if not cert.startswith(header):
1248 f.write(header + "\n")
1249 formatted = "\n".join([line for line in cert.splitlines() if line])
1250 f.write(formatted)
1251 if not cert.endswith(footer):
1252 f.write("\n" + footer)
1253 return certFile
1254
1255 def __readCertificateFromFile(self, file_):
1256 cert = None
1257 with open(file_, 'r') as f:
1258 first = f.readline()
1259 if first == DASHES + BEGIN + " " + CERTIFICATE + DASHES + "\n":
1260 cert = f.readlines()
1261 cert = [line for line in cert if line]
1262 cert = cert[:-1]
1263 cert = "\n".join(cert)
1264 else:
1265 cert = first + f.read()
1266 return cert
1267
1268 def __writePublicKeyToFile(self, key):
1269 return self.__writeKeyToFile(key, PUBLIC)
1270
1271 def __readPublicKeyFromFile(self, keyFile):
1272 return self.__readKeyFromFile(keyFile, PUBLIC)
1273
1274 def __readPrivateKeyFromFile(self, keyFile):
1275 return self.__readKeyFromFile(keyFile, PRIVATE)
1276
1277 def __writePrivateKeyToFile(self, key):
1278 return self.__writeKeyToFile(key, PRIVATE)
1279
1280 def __writeKeyToFile(self, key, keyname):
1281 keyFile = self.__temporaryFile()
1282 if not self.__probablyBase64(key):
1283 key = base64.b64encode(key)
1284 publicKey = textwrap.fill(key, 63)
1285 # print "KEY"
1286 # print publicKey
1287 with open(keyFile, 'w') as f:
1288 f.write(DASHES + BEGIN + " " + keyname + " " + KEY + DASHES + "\n")
1289 f.write(publicKey)
1290 f.write("\n" + DASHES + END + " " + keyname + " " + KEY + DASHES)
1291 return keyFile
1292
1293 def __readKeyFromFile(self, keyFile, keyname):
1294 key = None
1295 with open(keyFile) as f:
1296 key = f.read()
1297 if key.startswith(DASHES + BEGIN + " " + keyname + " " + KEY + DASHES + "\n"):
1298 key = "".join(key.splitlines()[1:-1])
1299 if "\\r\\n" in key:
1300 key = key.replace("\\r\\n", "")
1301 if self.__probablyBase64(key):
1302 key = base64.b64decode(key)
1303 return key
1304
1305 def _getUsername(self):
1306 getr = self.kmsClient.get(self.kmsUrl + self.usernamesPrefix)
1307 username = None
1308 if getr.json:
1309 try:
1310 username = getr.json[0]
1311 except KeyError:
1312 print getr.json
1313 if username:
1314 myCert = self._getPublicCertFor(username)
1315 if not myCert.certificate:
1316 self._setPublicCert(username)
1317 elif not self._checkPublicCert(myCert):
1318 print "Woah, your certificate isn't valid!"
1319 if self.headless:
1320 sys.exit(1)
1321 else:
1322 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): ")
1323 if reset == 'Y' or reset == 'y' or reset == 'yes' or reset == "YES":
1324 self._setPublicCert(username)
1325 else: sys.exit(0)
1326
1327 return username
1328
1329 def _generateKey(self):
1330 """128-bit Pseudo-Random Key"""
1331 return os.urandom(16)
1332
1333 def _generateIV(self):
1334 """128-bit IV"""
1335 return os.urandom(16)
1336
1337 def _generateKeypair(self, filename=None, encrypt=True):
1338 if not filename:
1339 filename = self.__temporaryFile()
1340 unencrypted = self.__temporaryFile()
1341 if self.password or not encrypt:
1342 # argList = [self.openSSL, 'genrsa', '-aes128', '-out', filename, '-passout', 'pass:' + self.password, '4096']
1343 argList = [self.openSSL, 'genrsa', '-out', unencrypted, '4096']
1344 try:
1345 okay = subprocess.call(argList)
1346 if okay != 0:
1347 self.__remove(unencrypted)
1348 unencrypted = False
1349 filename = False
1350 elif encrypt:
1351 argList = [self.openSSL, 'pkcs8', '-topk8', '-v2', 'aes128', '-in', unencrypted, '-out', filename, '-passout', 'pass:' + self.password]
1352 try:
1353 okay = subprocess.call(argList)
1354 if okay != 0:
1355 filename = False
1356 except TypeError:
1357 filename = False
1358 else:
1359 self.__remove(filename)
1360 filename = unencrypted
1361 except TypeError:
1362 self.__remove(filename)
1363 filename = False
1364 else:
1365 self.__remove(filename)
1366 filename = False
1367 if encrypt:
1368 self.__remove(unencrypted)
1369 return filename
1370
1371 def _generatePKCS1Keypair(self):
1372 filename = self.__temporaryFile()
1373 certFile = self.__temporaryFile()
1374 confFile = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'certs.conf')
1375 days = 365*20
1376 argList = [self.openSSL, 'req', '-x509', '-out', certFile, '-newkey', 'rsa:4096', '-keyout', filename, '-days', unicode(days), '-nodes', '-config', confFile]
1377 try:
1378 okay = subprocess.call(argList)
1379 if okay != 0:
1380 self.__remove(filename)
1381 filename = False
1382 except TypeError:
1383 self.__remove(filename)
1384 filename = False
1385 self.__remove(certFile)
1386 return filename
1387
1388 def __makeRSACertificateFromPrivate(self, privateKeyFile):
1389 certificateFile = self.__temporaryFile()
1390 confFile = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'certs.conf')
1391 days = 365*20
1392 argList = [self.openSSL, 'req', '-new', '-x509', '-key', privateKeyFile, '-out', certificateFile, '-days', unicode(days), '-nodes', '-config', confFile]
1393 if self.password:
1394 argList.append("-passin")
1395 argList.append("pass:" + self.password)
1396 try:
1397 okay = subprocess.call(argList)
1398 if okay != 0:
1399 self.__remove(certificateFile)
1400 return False
1401 return certificateFile
1402 except TypeError:
1403 print "Error making certficate."
1404 print argList
1405 self.__remove(certificateFile)
1406
1407 def __publicKeyFromPrivate(self, privateKeyFile):
1408 publicKeyFile = self.__temporaryFile()
1409 argList = [self.openSSL, 'rsa', '-pubout', '-outform', 'DER', '-in', privateKeyFile, '-out', publicKeyFile]
1410 if self.password:
1411 argList.append("-passin")
1412 argList.append("pass:" + self.password)
1413 okay = subprocess.call(argList)
1414 if okay == 0:
1415 return publicKeyFile
1416 self.__remove(publicKeyFile)
1417 return False
1418
1419 def __probablyBase64(self, s):
1420 return (len(''.join(s.split())) % 4 == 0) and re.match('^[A-Za-z0-9+/]+[=]{0,2}$', s)
1421
1422 def __cleanString(self, s):
1423 openTag = "<string>"
1424 closeTag = "</string>"
1425
1426 if s.startswith(openTag):
1427 return s[len(openTag):-len(closeTag)]
1428 s = s.replace("\\r\\n", "\n")
1429 return s
1430
1431 def __optimalPieceSize(self, totalSize):
1432 Dpieces = totalSize / 100
1433 if Dpieces < self.pieceSize:
1434 return self.pieceSize
1435 return Dpieces
1436
1437 def __remove(self, path):
1438 try:
1439 os.remove(path)
1440 except OSError:
1441 pass
1442
1443 class Store(object):
1444 id = None
1445 index_codename = None
1446 friendly_name = None
1447 owner = None
1448 iv = None
1449 # readers = []
1450 # writers = []
1451 # administrators = []
1452
1453 def __init__(self, props=None, index_codename="", friendly_name="", filename="", owner="", readers=None, writers=None, administrators=None, iv=None):
1454 self.readers = []
1455 self.writers = []
1456 self.administrators = []
1457 if props:
1458 self.id = props['id']
1459 self.index_codename = props['indexCodename']
1460 self.friendly_name = props['friendly_name']
1461 self.owner = props['owner']
1462 self.readers = props['readers']
1463 self.writers = props['writers']
1464 self.administrators = props['administrators']
1465 if props['iv']:
1466 self.iv = base64.b64decode(props['iv'])
1467 else:
1468 self.index_codename = index_codename
1469 self.friendly_name = friendly_name
1470 # self.filename = filename
1471 self.owner = owner
1472 if readers:
1473 self.readers = readers
1474 if writers:
1475 self.writers = writers
1476 if administrators:
1477 self.administrators = administrators
1478 self.iv = iv
1479
1480 def __str__(self):
1481 message = u'\n' + unicode(self.friendly_name)
1482 message += u'\n\tOwner: ' + unicode(self.owner)
1483 message += u'\n\tReaders: ' + unicode(self.readers)
1484 message += u'\n\tWriters: ' + unicode(self.writers)
1485 message += u'\n\tAdmins: ' + unicode(self.administrators)
1486 return message
1487
1488 def __repr__(self):
1489 return str(self)
1490
1491 def dict(self):
1492 this = {
1493 "id": self.id,
1494 "indexCodename": self.index_codename,
1495 "friendly_name": self.friendly_name,
1496 # "fileName": self.filename,
1497 "owner": self.owner,
1498 "readers": self.readers,
1499 "writers": self.writers,
1500 "administrators": self.administrators
1501 }
1502 if self.iv:
1503 this["iv"] = base64.b64encode(self.iv)
1504 return this
1505
1506
1507 class StoreProperties(object):
1508 sas_url = ""
1509 providers = []
1510 kms_url = ""
1511 kms_user = None
1512 ims_url = ""
1513 ims_user = None
1514 bucket = None
1515 public_key_bytes = None
1516 private_key_bytes = None
1517 ims_public_key_bytes = None
1518
1519 def __init__(self, eDict=None):
1520 self.providers = []
1521 if eDict:
1522 self.kms_url = eDict['kmsServiceUrl']
1523 self.private_key_bytes = base64.b64decode(eDict['privateKeyBytes'])
1524 self.sas_url = eDict['sasServiceUrl']
1525 self.ims_user = eDict['imsUser']
1526 self.providers = eDict['storageProviders']
1527 self.public_key_bytes = base64.b64decode(eDict['publicKeyBytes'])
1528 self.ims_url = eDict['imsServiceUrl']
1529 self.kms_user = eDict['kmsUser']
1530 self.bucket = eDict['workspace']
1531 self.ims_public_key_bytes = base64.b64decode(eDict['imsPublicKeyBytes'])
1532
1533 def dict(self):
1534 return {'kmsServiceUrl': self.kms_url,
1535 'privateKeyBytes': binascii.b2a_base64(self.private_key_bytes),
1536 'sasServiceUrl': self.sas_url,
1537 'imsUser': self.ims_user,
1538 'storageProviders': self.providers,
1539 'publicKeyBytes': binascii.b2a_base64(self.public_key_bytes),
1540 'imsServiceUrl': self.ims_url,
1541 'kmsUser': self.kms_user,
1542 'workspace': self.bucket,
1543 'imsPublicKeyBytes': binascii.b2a_base64(self.ims_public_key_bytes)}
1544
1545 def __repr__(self):
1546 return json.dumps(self.dict(), sort_keys=True, indent=4)
1547
1548
1549 class UserPublicCertificate(object):
1550 username = None
1551 certificate = None
1552
1553 def __init__(self, props=None, username="", certificate=None):
1554 if props:
1555 self.username = props['username']
1556 self.certificate = props['key']
1557 else:
1558 self.username = username
1559 self.certificate = certificate
1560
1561 def dict(self):
1562 return {
1563 'username': self.username,
1564 'key': self.certificate
1565 }
1566
1567 def __repr__(self):
1568 return json.dumps(self.dict(), sort_keys=True, indent=4)
1569
1570 class UserPrivateKey(UserPublicCertificate):
1571 pass
1572
1573
1574 class Config(object):
1575 def __init__(self, ims, kms, key, secret):
1576 self.kmsUrl = kms
1577 self.imsUrl = ims
1578 self.client_key = key
1579 self.client_secret = secret
1580
1581
1582 class TrustStoreClientAuthenticationException(Exception):
1583 def __init__(self, message, isConfigured=False):
1584 self.message = message
1585 self.isConfigured = isConfigured
1586
1587 def __str__(self):
1588 msg = repr(self.message) + " note: "
1589 if self.isConfigured:
1590 msg += " login attempt was made."
1591 else:
1592 msg += " not currently authenticated"
1593 return msg
1594
1595
1596 class OpenSSLVersionException(Exception):
1597 def __init__(self, version):
1598 self.version = version
1599
1600 def __str__(self):
1601 return "The version of openssl (" + self.version + ") is too old!"