From: david-sarah Date: Sun, 11 Jul 2010 21:37:21 +0000 (-0700) Subject: Move EncryptedTemporaryFile from SFTP frontend to allmydata.util.fileutil, and make... X-Git-Url: https://git.rkrishnan.org/architecture.txt?a=commitdiff_plain;h=05022dca36780b3bfd3faba1f05b557178535f78;p=tahoe-lafs%2Ftahoe-lafs.git Move EncryptedTemporaryFile from SFTP frontend to allmydata.util.fileutil, and make the FTP frontend also use it (fixing #1083). --- diff --git a/src/allmydata/frontends/ftpd.py b/src/allmydata/frontends/ftpd.py index 8e66e437..1321863a 100644 --- a/src/allmydata/frontends/ftpd.py +++ b/src/allmydata/frontends/ftpd.py @@ -1,5 +1,4 @@ -import tempfile from zope.interface import implements from twisted.application import service, strports from twisted.internet import defer @@ -10,6 +9,7 @@ from twisted.protocols import ftp from allmydata.interfaces import IDirectoryNode, ExistingChildError, \ NoSuchChildError from allmydata.immutable.upload import FileHandle +from allmydata.util.fileutil import EncryptedTemporaryFile class ReadFile: implements(ftp.IReadFile) @@ -27,7 +27,7 @@ class FileWriter: raise NotImplementedError("Non-streaming producer not supported.") # we write the data to a temporary file, since Tahoe can't do # streaming upload yet. - self.f = tempfile.TemporaryFile() + self.f = EncryptedTemporaryFile() return None def unregisterProducer(self): diff --git a/src/allmydata/frontends/sftpd.py b/src/allmydata/frontends/sftpd.py index 5980effb..a38196fc 100644 --- a/src/allmydata/frontends/sftpd.py +++ b/src/allmydata/frontends/sftpd.py @@ -1,5 +1,5 @@ -import os, tempfile, heapq, binascii, traceback, array, stat, struct +import heapq, traceback, array, stat, struct from types import NoneType from stat import S_IFREG, S_IFDIR from time import time, strftime, localtime @@ -32,8 +32,7 @@ from allmydata.interfaces import IFileNode, IDirectoryNode, ExistingChildError, from allmydata.mutable.common import NotWriteableError from allmydata.immutable.upload import FileHandle from allmydata.dirnode import update_metadata - -from pycryptopp.cipher.aes import AES +from allmydata.util.fileutil import EncryptedTemporaryFile noisy = True use_foolscap_logging = True @@ -288,56 +287,6 @@ def _direntry_for(filenode_or_parent, childname, filenode=None): return None -class EncryptedTemporaryFile(PrefixingLogMixin): - # not implemented: next, readline, readlines, xreadlines, writelines - - def __init__(self): - PrefixingLogMixin.__init__(self, facility="tahoe.sftp") - self.file = tempfile.TemporaryFile() - self.key = os.urandom(16) # AES-128 - - def _crypt(self, offset, data): - # TODO: use random-access AES (pycryptopp ticket #18) - offset_big = offset // 16 - offset_small = offset % 16 - iv = binascii.unhexlify("%032x" % offset_big) - cipher = AES(self.key, iv=iv) - cipher.process("\x00"*offset_small) - return cipher.process(data) - - def close(self): - self.file.close() - - def flush(self): - self.file.flush() - - def seek(self, offset, whence=0): # 0 = SEEK_SET - if noisy: self.log(".seek(%r, %r)" % (offset, whence), level=NOISY) - self.file.seek(offset, whence) - - def tell(self): - offset = self.file.tell() - if noisy: self.log(".tell() = %r" % (offset,), level=NOISY) - return offset - - def read(self, size=-1): - if noisy: self.log(".read(%r)" % (size,), level=NOISY) - index = self.file.tell() - ciphertext = self.file.read(size) - plaintext = self._crypt(index, ciphertext) - return plaintext - - def write(self, plaintext): - if noisy: self.log(".write()" % (len(plaintext),), level=NOISY) - index = self.file.tell() - ciphertext = self._crypt(index, plaintext) - self.file.write(ciphertext) - - def truncate(self, newsize): - if noisy: self.log(".truncate(%r)" % (newsize,), level=NOISY) - self.file.truncate(newsize) - - class OverwriteableFileConsumer(PrefixingLogMixin): implements(IFinishableConsumer) """I act both as a consumer for the download of the original file contents, and as a diff --git a/src/allmydata/util/fileutil.py b/src/allmydata/util/fileutil.py index 46e1c9bf..f671f7ea 100644 --- a/src/allmydata/util/fileutil.py +++ b/src/allmydata/util/fileutil.py @@ -2,10 +2,13 @@ Futz with files like a pro. """ -import sys, exceptions, os, stat, tempfile, time +import sys, exceptions, os, stat, tempfile, time, binascii from twisted.python import log +from pycryptopp.cipher.aes import AES + + def rename(src, dst, tries=4, basedelay=0.1): """ Here is a superkludge to workaround the fact that occasionally on Windows some other process (e.g. an anti-virus scanner, a local search @@ -112,6 +115,49 @@ class NamedTemporaryDirectory: if self.cleanup and hasattr(self, 'name'): rm_dir(self.name) +class EncryptedTemporaryFile: + # not implemented: next, readline, readlines, xreadlines, writelines + + def __init__(self): + self.file = tempfile.TemporaryFile() + self.key = os.urandom(16) # AES-128 + + def _crypt(self, offset, data): + offset_big = offset // 16 + offset_small = offset % 16 + iv = binascii.unhexlify("%032x" % offset_big) + cipher = AES(self.key, iv=iv) + cipher.process("\x00"*offset_small) + return cipher.process(data) + + def close(self): + self.file.close() + + def flush(self): + self.file.flush() + + def seek(self, offset, whence=0): # 0 = SEEK_SET + self.file.seek(offset, whence) + + def tell(self): + offset = self.file.tell() + return offset + + def read(self, size=-1): + index = self.file.tell() + ciphertext = self.file.read(size) + plaintext = self._crypt(index, ciphertext) + return plaintext + + def write(self, plaintext): + index = self.file.tell() + ciphertext = self._crypt(index, plaintext) + self.file.write(ciphertext) + + def truncate(self, newsize): + self.file.truncate(newsize) + + def make_dirs(dirname, mode=0777): """ An idempotent version of os.makedirs(). If the dir already exists, do