def main(): q = LockboxOrderedDict() observer = Observer() observer.schedule(CleartextEventHandler(q, cleartext_dir, ciphertext_dir), path=cleartext_dir, recursive=True) observer.schedule(CiphertextEventHandler(q, cleartext_dir, ciphertext_dir), path=ciphertext_dir, recursive=True) observer.start() try: while True: time.sleep(1) try: while True: (stem, deets) = q.first() #just read, don't pop here because we need it to stay in the OrderedDict in case another event comes in on this file before we're done logger.debug('%s %s', stem, deets) if not deets['collided']: check_for_collided_after_operation = False if deets['action'] == 'encrypt': logger.debug('encrypting from %s to %s', deets['cleartext_full_path'], deets['ciphertext_full_path']) check_for_collided_after_operation = True target_path = deets['ciphertext_full_path'] (tmp_fd, tmp_path) = tempfile.mkstemp() os.close(tmp_fd) rio = BIO.openfile(deets['cleartext_full_path'], 'rb') rio.write_close() wrio = BIO.openfile(tmp_path, 'wb') cf = BIO.CipherStream(wrio) salt = Rand.rand_pseudo_bytes(8)[0] logger.debug("salt=%s",ByteToHex(salt)) passphrase_f = open(os.path.join(deets['config_dir_path'], 'key.txt'), 'r') ## TODO cache this passphrase = passphrase_f.readline().rstrip() passphrase_f.close() (key, iv) = m2.bytes_to_key(m2.aes_256_cbc(), m2.md5(), passphrase, salt, 1) logger.debug("key=%s", ByteToHex(key)) logger.debug("iv=%s", ByteToHex(iv)) cf.set_cipher('aes_256_cbc', key, iv, 1) wrio.write('Salted__') # magic - see openssl's enc.c wrio.write(salt) while True: out = rio.read(4096) if not out: break cf.write(out) cf.flush() cf.write_close() cf.close() wrio.flush() wrio.write_close() wrio.close() elif deets['action'] == 'decrypt': logger.debug('decrypting from %s to %s', deets['ciphertext_full_path'], deets['cleartext_full_path']) check_for_collided_after_operation = True target_path = deets['cleartext_full_path'] (tmp_fd, tmp_path) = tempfile.mkstemp() os.close(tmp_fd) ## TODO: handle failure of decryption rio = BIO.openfile(deets['ciphertext_full_path'], 'rb') rio.write_close() wrio = BIO.openfile(deets['cleartext_full_path'], 'wb') cf = BIO.CipherStream(wrio) header = rio.read(size=len('Salted__')) if header != 'Salted__': print "uh, this doesn't look like an encrypted file" exit() salt = rio.read(size=8) logger.debug("salt=%s",ByteToHex(salt)) passphrase_f = open(os.path.join(deets['config_dir_path'], 'key.txt'), 'r') ## TODO cache this passphrase = passphrase_f.readline().rstrip() passphrase_f.close() (key, iv) = m2.bytes_to_key(m2.aes_256_cbc(), m2.md5(), passphrase, salt, 1) logger.debug("key=%s", ByteToHex(key)) logger.debug("iv=%s", ByteToHex(iv)) cf.set_cipher('aes_256_cbc', key, iv, 0) while True: out = rio.read(4096) if not out: break cf.write(out) cf.flush() cf.write_close() cf.close() wrio.flush() wrio.write_close() wrio.close() elif deets['action'] == 'delete-cipher': logger.debug('deleting %s', deets['ciphertext_full_path']) if os.path.exists(deets['ciphertext_full_path']): os.unlink(deets['ciphertext_full_path']) elif deets['action'] == 'delete-clear': logger.debug('deleting %s', deets['cleartext_full_path']) if os.path.exists(deets['cleartext_full_path']): os.unlink(deets['cleartext_full_path']) else: logger.error('Unknown action %s on %s', deets['action'], deets) time.sleep(0) ## http://stackoverflow.com/questions/787803/how-does-a-threading-thread-yield-the-rest-of-its-quantum-in-python (stem, deets) = q.popitem(False) if check_for_collided_after_operation: ## now, if it got marked as collided in the interim while we were encrypting/decrypting then we don't want to overwrite if not deets['collided']: ## move from temp location to real location if platform.system() == 'Windows': ## TODO: this can throw an exception on windows if the target_place is locked. need to think about how to deal with this if os.path.exists(target_path): logger.debug('windows only: unlink %s', target_path) os.unlink(target_path) logger.debug('rename %s to %s', tmp_path, target_path) mkdir_p(os.path.dirname(target_path)) os.rename(tmp_path, target_path) else: ## TODO: if it was a decrypt, move the newly decrypted file to stem-from-dropbox-collision ## if it was an encrypt, immediately decrypt ciphertext version to stem-from-dropbox-collision ## TODO: delete tmp_path in the case that we're no longer using it (it was an encrypt that we no longer can copy to the ciphertext location) pass else: (stem, deets) = q.popitem(False) if deets['action'] in ('delete-clear','delete-cipher'): logger.error('should not get a collided delete because they should be removed from the queue in our OrderedDict subclass') else: ## TODO: immediately decrypt and put it into stem-from-dropbox-collision pass except StopIteration: #print "nada" pass except KeyboardInterrupt: pass observer.stop() observer.join()
return ''.join( bytes ) rio = BIO.openfile(os.path.expanduser('~/Lockbox/foobar/firsttest.txt'), 'rb') rio.write_close() wrio = BIO.openfile(os.path.expanduser('~/Dropbox/LOCKBOX-foobar/firsttest.txt'), 'wb') cf = BIO.CipherStream(wrio) salt = Rand.rand_pseudo_bytes(8)[0] #salt = '\x00\x00\x00\x00\x00\x00\x00\x00' print "salt=%s" % ByteToHex(salt) passphrase = 'foo bar' (key, iv) = m2.bytes_to_key(m2.aes_256_cbc(), m2.md5(), passphrase, salt, 1) print "key=%s" % ByteToHex(key) print "iv=%s" % ByteToHex(iv) cf.set_cipher('aes_256_cbc', key, iv, 1) wrio.write('Salted__') # magic - see openssl's enc.c wrio.write(salt) while True: out = rio.read(4096) if not out: break cf.write(out)