コード例 #1
0
ファイル: tinfoil.py プロジェクト: abaur/TinfoilP2P
  def join(self, knownNodes):
    """Join the social network.
    Calculate our userID and join network at given place.
    This involves:
    - generating a random ID by solving cryptographic puzzles,
      which serves as a guard against Sybil attacks.
    - generate public and private keys for new id.
    - notifying and requesting involved parties of the selected position.
    OR if the user has already created his ID in the past.
    - use the previously established private key to authenticate in network.
    """
    # Check to see if we have already been authenticated with the network.
    id = None
    rsaKey = None
    x = None

    if os.path.exists(constants.PATH_TO_ID % self.udpPort):
      fID = open(constants.PATH_TO_ID % self.udpPort, 'r')
      id = fID.read()
      fID.close()

      fKey = open(constants.PATH_TO_RSAKEY % self.udpPort, 'r')
      rsaKey = Crypto.PublicKey.RSA.importKey(pickle.load(fKey))
      fKey.close()

      fX = open(constants.PATH_TO_X % self.udpPort, 'r')
      x = long(fX.read())
      fX.close()

    # Generate new node from scratch or based on already known values.
    self.node = TintangledNode(id = id, udpPort = self.udpPort, vanillaEntangled = self.vanillaEntangled)

    # Save node data to file if node is new.
    if id == None:
      # Save ID, RSAKey and X to file.
      fID = open(constants.PATH_TO_ID % self.udpPort, 'w')
      fID.write(self.node.id)
      fID.close()

      fKey = open(constants.PATH_TO_RSAKEY % self.udpPort, 'w')
      pickle.dump(self.node.rsaKey.exportKey(), fKey)
      fKey.close()

      fX = open(constants.PATH_TO_X % self.udpPort, 'w')
      fX.write(str(self.node.x))
      fX.close()
    else:
      self.node.rsaKey = rsaKey
      self.node.x = x
      # Alternative: add rsaKey and x as optional parameters in
      # TintangledNode.__init__.

    self.node.joinNetwork(knownNodes)
    print('Your ID is: %s   - Tell your friends!' % self.node.id.encode('hex'))
    self.node.keyCache[self.node.id] = self.node.rsaKey
    # Add ourself to our friends list, so we can see our own posts too..
    self.addFriend(self.node.id)
    self.node.publishData(
        ('%s:publickey' % (self.node.id)),
        pickle.dumps(self._getUserPublicKey(self.node.id).publickey()))
    twisted.internet.reactor.run()
コード例 #2
0
ファイル: tinfoil.py プロジェクト: abaur/TinfoilP2P
class Client:
  '''A TinFoil Net client
  Builds the "social" features ontop of the underlying network framework.
  '''

  def __init__(self, udpPort = 4000, vanillaEntangled = False):
    '''Initializes a Tinfoil Node.'''
    self.udpPort = udpPort
    self.postCache = {}
    # TODO(cskau): we need to ask the network for last known sequence number
    self.sequenceNumber = 0
    self.friends = set()
    # TODO(cskau): Retrieve these every time we re-join.
    self.sharingKeys = {}
    self.postIDNameTuple = {}
    self.vanillaEntangled = vanillaEntangled

  def join(self, knownNodes):
    """Join the social network.
    Calculate our userID and join network at given place.
    This involves:
    - generating a random ID by solving cryptographic puzzles,
      which serves as a guard against Sybil attacks.
    - generate public and private keys for new id.
    - notifying and requesting involved parties of the selected position.
    OR if the user has already created his ID in the past.
    - use the previously established private key to authenticate in network.
    """
    # Check to see if we have already been authenticated with the network.
    id = None
    rsaKey = None
    x = None

    if os.path.exists(constants.PATH_TO_ID % self.udpPort):
      fID = open(constants.PATH_TO_ID % self.udpPort, 'r')
      id = fID.read()
      fID.close()

      fKey = open(constants.PATH_TO_RSAKEY % self.udpPort, 'r')
      rsaKey = Crypto.PublicKey.RSA.importKey(pickle.load(fKey))
      fKey.close()

      fX = open(constants.PATH_TO_X % self.udpPort, 'r')
      x = long(fX.read())
      fX.close()

    # Generate new node from scratch or based on already known values.
    self.node = TintangledNode(id = id, udpPort = self.udpPort, vanillaEntangled = self.vanillaEntangled)

    # Save node data to file if node is new.
    if id == None:
      # Save ID, RSAKey and X to file.
      fID = open(constants.PATH_TO_ID % self.udpPort, 'w')
      fID.write(self.node.id)
      fID.close()

      fKey = open(constants.PATH_TO_RSAKEY % self.udpPort, 'w')
      pickle.dump(self.node.rsaKey.exportKey(), fKey)
      fKey.close()

      fX = open(constants.PATH_TO_X % self.udpPort, 'w')
      fX.write(str(self.node.x))
      fX.close()
    else:
      self.node.rsaKey = rsaKey
      self.node.x = x
      # Alternative: add rsaKey and x as optional parameters in
      # TintangledNode.__init__.

    self.node.joinNetwork(knownNodes)
    print('Your ID is: %s   - Tell your friends!' % self.node.id.encode('hex'))
    self.node.keyCache[self.node.id] = self.node.rsaKey
    # Add ourself to our friends list, so we can see our own posts too..
    self.addFriend(self.node.id)
    self.node.publishData(
        ('%s:publickey' % (self.node.id)),
        pickle.dumps(self._getUserPublicKey(self.node.id).publickey()))
    twisted.internet.reactor.run()

  def share(self, resourceID, friendsID):
    """Share some stored resource with one or more users.
    Allow other user(s) to access store resource by issuing sharing key
    unique to the user-resource pair.
    Code Sketch:
      sharingKey[resourceID][otherUserID] = encrypt(
      publicKeys[otherUserID],
      resourceKeys[resourceID])
      store(
        "SharingKey(resourceID, otherUserID)",
        sharingKeys[resourceID][otherUserID])
    """
    if not resourceID in self.sharingKeys:
      print('Error: Can\'t share. Post sharing key not found (yet).')
    else:
      sharingKeyName = ('%s:share:%s' % (resourceID, friendsID))
      sharingKeyEncrypted = self._encryptForUser(
          self.sharingKeys[resourceID],
          friendsID)
      # We might not have user's public key yet..
      if sharingKeyEncrypted is not None:
        print('Storing sharing key for: %s : %s' % (
            util.bin2hex(resourceID),
            util.bin2hex(friendsID),
            ))
        self.node.publishData(sharingKeyName, sharingKeyEncrypted)
      else:
        print('Couldn\'t share. Friend\'s public key not found.')

  def _encryptForUser(self, content, userID, callback = None):
    """Encrypt some content asymetrically with user's public key."""
    userKey = self._getUserPublicKey(userID, callback)
    if userKey is None:
      return None
    return self._encryptKey(content, userKey)

  def _getUserPublicKey(self, userID, callback = None):
    """Returns the public key corresponding to the specified userID, if any."""
    if userID in self.node.keyCache:
      return self.node.keyCache[userID]
    publicKeyName = ('%s:publickey' % (userID))
    publicKeyID = self.node.getNameID(publicKeyName)
    # TODO(cskau): This is a defer !!
    publicKeyDefer = self.node.iterativeFindValue(publicKeyID)
    def _addPublicKeyToLocalCache(result):
      if type(result) == dict:
        for r in result:
          self.node.keyCache[userID] = pickle.loads(result[r])
      else:
        print('Could not find public key for: %s' % (
            util.bin2hex(userID)
            ))
    publicKeyDefer.addCallback(_addPublicKeyToLocalCache)
    if callback is not None:
      publicKeyDefer.addCallback(callback)
    return None

  def _encryptKey(self, content, publicKey):
    """Encrypts content (sharing key) using the specified public key."""
    return publicKey.encrypt(content, '') # '' -> K not needed when using RSA.

  def _decryptKey(self, content):
    """Decrypts content (sharing key) using node's own private key."""
    return self.RSAkey.decrypt(content)

  def unshare(self, resourceID, friendsID):
    """ Unshare previously shared resource with one of more users.
    Ask network to delete specific, existing sharing keys.
    Note:
    This can never be safer than the network allows it.
    Malicious peer might simply keep the sharing keys despite all.
    Code Sketch:
      weakDelete(sharingKeys[resourceID][otherUserID])
    """
    shareKeyID = ('%s:share:%s' % (resourceID, friendsID))
    self.node.removeDate(shareKeyID)

  def post(self, content):
    """ Post some resource to the network.
    Ask the network to store some (encrypted) resource.
    Note:
    This should be encrypted with a symmetric key which will be private
    until shared through the above share() method.
    """
    newSequenceNumber = self._getSequenceNumber()
    postKey = util.generateRandomString(constants.SYMMETRIC_KEY_LENGTH)
    #nonce = util.generateRandomString(constants.NONCE_LENGTH)
    # Use sequence number as nonce - that way we dont need to include it
    nonce = util.int2bin(newSequenceNumber, nbytes = constants.NONCE_LENGTH)
    encryptedContent = self._encryptPost(postKey, nonce, content)
    postName = ('%s:post:%s' % (self.node.id, newSequenceNumber))
    postID = self.node.getNameID(postName)
    # We need to remember post keys used so we can issue sharing keys later
    self.sharingKeys[postID] = postKey
    postDefer = self.node.publishData(postName, encryptedContent)
    # update our latest sequence number
    latestName = ('%s:latest' % (self.node.id))
    latestDefer = self.node.publishData(latestName, newSequenceNumber)
    # store post key by sharing the post with ourself
    postDefer.addCallback(lambda result: self.share(postID, self.node.id))

  def _getSequenceNumber(self):
    """Return next, unused sequence number unique to this user."""
    # TODO(cskau): we probably need to ask the network to avoid sync errors.
    #  Case: a user might publish from multiple clients at a time.
    self.sequenceNumber += 1
    return self.sequenceNumber

  def _encryptPost(self, key, nonce, post):
    """Encrypt a post with a symmetric key.

    @param key: must be 16, 24, or 32 bytes long.
    @type key: str

    """

    if not len(key) in [16, 24, 32]:
      raise Exception(
          'Specified key had an invalid key length, it should be 16, 24 or 32.')
    if len(nonce) != constants.NONCE_LENGTH:
      raise Exception(
          'Specified nonce had an invalid key length, it should be 16.')

    aesKey = Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_CBC, nonce)
    # NOTE(cskau): *input* has to be a 16-multiple, pad with whitespace
    return aesKey.encrypt(post + (' ' * (16 - (len(post) % 16))))

  def _decryptPost(self, key, nonce, post):
    """Decrypt a post with a symmetric key.

    @param key: must be 16, 24, or 32 bytes long.
    @type key: str

    """

    if not len(key) in [16, 24, 32]:
      raise Exception(
          'Specified key had an invalid key length, it should be 16, 24 or 32.')
    if len(nonce) != constants.NONCE_LENGTH:
      raise Exception(
          'Specified nonce had an invalid key length, it should be 16.')

    aesKey = Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_CBC, nonce)
    decryptedMessage = aesKey.decrypt(post)
    # remove any whitespace padding.
    return decryptedMessage.strip()

  def _processUpdatesResult(self, result):
    """Process post updates when we get them as callbacks."""
    for resultKey in result:
      if isinstance(resultKey, entangled.kademlia.contact.Contact):
        print("WARN: key not found!")
        return
      # for some reason a contact is mixed in at times..
      if not isinstance(result[resultKey], entangled.kademlia.contact.Contact):
        postID = resultKey
        friendsID, n = self.postIDNameTuple[postID]
        self.postCache[friendsID][n] = {
          'post': result[postID],
          'id': postID,
        }

  def getUpdates(self, friendsID, lastKnownSequenceNumber):
    """ Check for and fetch new updates on user(s)
    Ask for latest known post from a given user and fetch delta since last
     fetched update.
    Code Sketch:
      latestSequenceNumber = get("latest(otherUserID)")
      latestPostID = hash(otherUserID + latestSequenceNumber)
      latestPost = get(latestPostID)
    """
    keyName = '%s:latest' % (friendsID)
    keyID = self.node.getNameID(keyName)
    def _processSequenceNumber(result):
      if type(result) == dict:
        lastSequenceNumber = int(result[keyID])
        for n in range(lastKnownSequenceNumber, (lastSequenceNumber + 1)):
          # There isn't actually any post 0 (which is kinda stupid..)
          if n == 0:
            continue
          postName = ('%s:post:%s' % (friendsID, n))
          postID = self.node.getNameID(postName)
          self.postIDNameTuple[postID] = (friendsID, n)
          # ask network for updates
          self.node.iterativeFindValue(postID).addCallback(
              self._processUpdatesResult)
      else:
        print('Could not find sequence number for: %s' % (
            util.bin2hex(friendsID)))
    self.node.iterativeFindValue(keyID).addCallback(_processSequenceNumber)
    # NOTE(cskau): it's all deferred so we can't do much here
    # TODO(cskau): maybe just return cache?

  ## ---- "Soft" API ----

  def addFriend(self, friendsID):
    """Adds the specified friendsID to the user's friend set."""
    if len(friendsID) != constants.ID_LENGTH:
      raise 'Malformed ID'
    self.friends.add(friendsID)
    self.postCache[friendsID] = {}

  def getDigest(self, n = 10):
    """Gets latest n updates from friends."""
    digest = {}
    for f in self.friends:
      # update post cache
      lastKnownPost = max([0] + self.postCache[f].keys())
      # Do eventual update of cache
      #  Note: unfortunaly we can't block and wait for updates, so make do
      self.getUpdates(f, lastKnownPost)
    for f in self.friends:
      for k in self.postCache[f].keys()[-n:]:
        postID = self.postCache[f][k]['id']
        if postID in self.sharingKeys:
          self.postCache[f][k].update({'key': self.sharingKeys[postID]})
          self.postCache[f][k].update({'postp':
              self._decryptPost(
                  self.sharingKeys[postID],
                  util.int2bin(k, nbytes = constants.NONCE_LENGTH),
                  self.postCache[f][k]['post'])})
        else:
          sharingKeyName = ('%s:share:%s' % (postID, self.node.id))
          sharingKeyID = self.node.getNameID(sharingKeyName)
          def _createClosureForPSKR(postID):
            def _processSharingKeyResult(result):
              if type(result) == dict:
                for r in result:
                  if not isinstance(result[r], entangled.kademlia.contact.Contact):
                    self.sharingKeys[postID] = self.node.rsaKey.decrypt(
                        result[r][0])
              else:
                print('Could not find sharing key for: %s : %s' % (
                    util.bin2hex(postID),
                    util.bin2hex(self.node.id),
                    ))
            return _processSharingKeyResult
          self.node.iterativeFindValue(sharingKeyID).addCallback(
              _createClosureForPSKR(postID))
      # get last n from this friend
      digest[f] = self.postCache[f].items()[-n:][::-1]
    return digest