def __init__(self, encrypted=None, certData=None, options={}): service.MultiService.__init__(self) if encrypted is None: if crypto: encrypted = True else: encrypted = False assert encrypted in (True, False) self.options = options self.listeners = [] self.locationHints = [] self.encrypted = encrypted if encrypted and not crypto: raise RuntimeError( "crypto for PB is not available, " "try importing twisted.pb.crypto and see " "what happens" ) if encrypted: if certData: cert = crypto.sslverify.PrivateCertificate.loadPEM(certData) else: cert = self.createCertificate() self.myCertificate = cert self.tubID = crypto.digest32(cert.digest("sha1")) else: self.myCertificate = None self.tubID = None # local Referenceables self.nameToReference = {} self.referenceToName = {} # remote stuff. Most of these use a TubRef (or NoAuthTubRef) as a # dictionary key self.tubConnectors = {} # maps TubRef to a TubConnector self.waitingForBrokers = {} # maps TubRef to list of Deferreds self.brokers = {} # maps TubRef to a Broker that connects to them self.unencryptedBrokers = [] # inbound Brokers without TubRefs
def __init__(self, encrypted=None, certData=None, options={}): service.MultiService.__init__(self) if encrypted is None: if crypto: encrypted = True else: encrypted = False assert encrypted in (True, False) self.options = options self.listeners = [] self.locationHints = [] self.encrypted = encrypted if encrypted and not crypto: raise RuntimeError("crypto for PB is not available, " "try importing twisted.pb.crypto and see " "what happens") if encrypted: if certData: cert = crypto.sslverify.PrivateCertificate.loadPEM(certData) else: cert = self.createCertificate() self.myCertificate = cert self.tubID = crypto.digest32(cert.digest("sha1")) else: self.myCertificate = None self.tubID = None # local Referenceables self.nameToReference = {} self.referenceToName = {} # remote stuff. Most of these use a TubRef (or NoAuthTubRef) as a # dictionary key self.tubConnectors = {} # maps TubRef to a TubConnector self.waitingForBrokers = {} # maps TubRef to list of Deferreds self.brokers = {} # maps TubRef to a Broker that connects to them self.unencryptedBrokers = [] # inbound Brokers without TubRefs
def evaluateHello(self, offer): """Evaluate the HELLO message sent by the other side. We compare TubIDs, and the higher value becomes the 'master' and makes the negotiation decisions. This method returns a tuple of DECISION,PARAMS. There are a few different possibilities: We are the master, we make a negotiation decision: DECISION is the block of data to send back to the non-master side, PARAMS are the connection parameters we will use ourselves. We are the master, we can't accomodate their request: raise NegotiationError We are not the master: DECISION is None """ if self.debugNegotiation: log.msg("evaluateHello(isClient=%s): offer=%s" % (self.isClient, offer,)) version = offer.get('banana-negotiation-version') if version != '1': raise NegotiationError("Unrecognized version number, " "'%s' not '1', in %s" % (version, offer)) forced = False f = offer.get('negotiation-forced', None) if f and f.lower() == "true": forced = True # 'forced' means the client is on a one-way link (or is really # stubborn) and has already made up its mind about the connection # parameters. If we are unable to handle exactly what they have # offered, we must hang up. assert not forced # TODO: implement # glyph says: look at Juice, it does rfc822 parsing, startTLS, # switch-to-other-protocol, etc. grep for retrieveConnection in q2q. # TODO: oh, if we see an HTTP client, send a good HTTP error like # "protocol not supported", or maybe even an HTML page that explains # what a PB server is # there are four distinct dicts here: # self.negotiationOffer: what we want # clientOffer: what they sent to us, the client's requests. # serverOffer: what we send to them, the server's decision # self.negotiationResults: the negotiated settings # # [my-tub-id] is not present in self.negotiationResults # the server's tubID is in [my-tub-id] for both self.negotiationOffer # and serverOffer # the client's tubID is in [my-tub-id] for clientOffer myTubID = self.myTubID theirTubID = offer.get("my-tub-id") if self.theirCertificate is None: # no client certificate if theirTubID is not None: # this is where a poor MitM attack is detected, one which # doesn't even pretend to encrypt the connection raise BananaError("you must use a certificate to claim a " "TubID") else: # verify that their claimed TubID matches their SSL certificate. # TODO: handle chains digest = crypto.digest32(self.theirCertificate.digest("sha1")) if digest != theirTubID: # this is where a good MitM attack is detected, one which # encrypts the connection but which of course uses the wrong # certificate raise BananaError("TubID mismatch") if theirTubID: theirTubRef = referenceable.TubRef(theirTubID) else: theirTubRef = None # unencrypted self.theirTubRef = theirTubRef # for use by non-master side, later if self.isClient and self.target.encrypted: # verify that we connected to the Tub we expected to. If we # weren't trying to connect to an encrypted tub, then don't # bother checking.. we just accept whoever we managed to connect # to. if theirTubRef != self.target: # TODO: how (if at all) should this error message be # communicated to the other side? raise BananaError("connected to the wrong Tub") if myTubID is None and theirTubID is None: iAmTheMaster = not self.isClient elif myTubID is None: iAmTheMaster = False elif theirTubID is None: iAmTheMaster = True else: # this is the most common case iAmTheMaster = myTubID > theirTubID if self.debugNegotiation: log.msg("iAmTheMaster: %s" % iAmTheMaster) decision, params = None, None if iAmTheMaster: # we get to decide everything decision = {} # first, do we continue with this connection? we might # have an existing connection for this particular tub if theirTubRef and theirTubRef in self.tub.brokers: # there is an existing connection, so drop this one if self.debugNegotiation: log.msg(" abandoning the connection: we already have one") raise NegotiationError("Duplicate connection") # combine their 'offer' and our own self.negotiationOffer to come # up with a 'decision' to be sent back to the other end, and the # 'params' to be used on our connection decision = {} decision['banana-decision-version'] = "1" ignoredKeys = ["my-tub-id"] us = dict([(k, self.negotiationOffer[k]) for k in self.negotiationOffer.keys() if k not in ignoredKeys]) them = dict([(k, offer[k]) for k in offer.keys() if k not in ignoredKeys]) if them != us: raise NegotiationError("our negotiation offers are different") params = {} else: # otherwise, the other side gets to decide pass if iAmTheMaster: # I am the master, so I send the decision if self.debugNegotiation: log.msg("Negotiation.sendDecision: %s" % decision) # now we send the decision and switch to Banana. they might hang # up. self.sendDecision(decision, params) else: # I am not the master, I receive the decision self.phase = DECIDING
def evaluateHello(self, offer): """Evaluate the HELLO message sent by the other side. We compare TubIDs, and the higher value becomes the 'master' and makes the negotiation decisions. This method returns a tuple of DECISION,PARAMS. There are a few different possibilities: We are the master, we make a negotiation decision: DECISION is the block of data to send back to the non-master side, PARAMS are the connection parameters we will use ourselves. We are the master, we can't accomodate their request: raise NegotiationError We are not the master: DECISION is None """ if self.debugNegotiation: log.msg("evaluateHello(isClient=%s): offer=%s" % ( self.isClient, offer, )) version = offer.get('banana-negotiation-version') if version != '1': raise NegotiationError("Unrecognized version number, " "'%s' not '1', in %s" % (version, offer)) forced = False f = offer.get('negotiation-forced', None) if f and f.lower() == "true": forced = True # 'forced' means the client is on a one-way link (or is really # stubborn) and has already made up its mind about the connection # parameters. If we are unable to handle exactly what they have # offered, we must hang up. assert not forced # TODO: implement # glyph says: look at Juice, it does rfc822 parsing, startTLS, # switch-to-other-protocol, etc. grep for retrieveConnection in q2q. # TODO: oh, if we see an HTTP client, send a good HTTP error like # "protocol not supported", or maybe even an HTML page that explains # what a PB server is # there are four distinct dicts here: # self.negotiationOffer: what we want # clientOffer: what they sent to us, the client's requests. # serverOffer: what we send to them, the server's decision # self.negotiationResults: the negotiated settings # # [my-tub-id] is not present in self.negotiationResults # the server's tubID is in [my-tub-id] for both self.negotiationOffer # and serverOffer # the client's tubID is in [my-tub-id] for clientOffer myTubID = self.myTubID theirTubID = offer.get("my-tub-id") if self.theirCertificate is None: # no client certificate if theirTubID is not None: # this is where a poor MitM attack is detected, one which # doesn't even pretend to encrypt the connection raise BananaError("you must use a certificate to claim a " "TubID") else: # verify that their claimed TubID matches their SSL certificate. # TODO: handle chains digest = crypto.digest32(self.theirCertificate.digest("sha1")) if digest != theirTubID: # this is where a good MitM attack is detected, one which # encrypts the connection but which of course uses the wrong # certificate raise BananaError("TubID mismatch") if theirTubID: theirTubRef = referenceable.TubRef(theirTubID) else: theirTubRef = None # unencrypted self.theirTubRef = theirTubRef # for use by non-master side, later if self.isClient and self.target.encrypted: # verify that we connected to the Tub we expected to. If we # weren't trying to connect to an encrypted tub, then don't # bother checking.. we just accept whoever we managed to connect # to. if theirTubRef != self.target: # TODO: how (if at all) should this error message be # communicated to the other side? raise BananaError("connected to the wrong Tub") if myTubID is None and theirTubID is None: iAmTheMaster = not self.isClient elif myTubID is None: iAmTheMaster = False elif theirTubID is None: iAmTheMaster = True else: # this is the most common case iAmTheMaster = myTubID > theirTubID if self.debugNegotiation: log.msg("iAmTheMaster: %s" % iAmTheMaster) decision, params = None, None if iAmTheMaster: # we get to decide everything decision = {} # first, do we continue with this connection? we might # have an existing connection for this particular tub if theirTubRef and theirTubRef in self.tub.brokers: # there is an existing connection, so drop this one if self.debugNegotiation: log.msg(" abandoning the connection: we already have one") raise NegotiationError("Duplicate connection") # combine their 'offer' and our own self.negotiationOffer to come # up with a 'decision' to be sent back to the other end, and the # 'params' to be used on our connection decision = {} decision['banana-decision-version'] = "1" ignoredKeys = ["my-tub-id"] us = dict([(k, self.negotiationOffer[k]) for k in self.negotiationOffer.keys() if k not in ignoredKeys]) them = dict([(k, offer[k]) for k in offer.keys() if k not in ignoredKeys]) if them != us: raise NegotiationError("our negotiation offers are different") params = {} else: # otherwise, the other side gets to decide pass if iAmTheMaster: # I am the master, so I send the decision if self.debugNegotiation: log.msg("Negotiation.sendDecision: %s" % decision) # now we send the decision and switch to Banana. they might hang # up. self.sendDecision(decision, params) else: # I am not the master, I receive the decision self.phase = DECIDING