Mercurial > repos > cathywise > truststore_import
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!" |