def createHandshake( self ): """ Create and return a ready-to-be-sent UniformDH handshake. The returned handshake data includes the public key, pseudo-random padding, the mark and the HMAC. If a UniformDH object has not been initialised yet, a new instance is created. """ assert self.sharedSecret is not None log.debug("Creating UniformDH handshake message.") if self.udh is None: self.udh = obfs3_dh.UniformDH() publicKey = self.udh.get_public() assert (const.MAX_PADDING_LENGTH - const.PUBLIC_KEY_LENGTH) >= 0 # Subtract the length of the public key to make the handshake on # average as long as a redeemed ticket. That should thwart statistical # length-based attacks. padding = mycrypto.strongRandom(random.randint(0, const.MAX_PADDING_LENGTH - const.PUBLIC_KEY_LENGTH)) # Add a mark which enables efficient location of the HMAC. mark = mycrypto.HMAC_SHA256_128(self.sharedSecret, publicKey) # Authenticate the handshake including the current approximate epoch. mac = mycrypto.HMAC_SHA256_128(self.sharedSecret, publicKey + padding + mark + util.getEpoch()) return publicKey + padding + mark + mac
def createHandshake(self): """ Create and return a ready-to-be-sent UniformDH handshake. The returned handshake data includes the public key, pseudo-random padding, the mark and the HMAC. If a UniformDH object has not been initialised yet, a new instance is created. """ assert self.sharedSecret is not None log.debug("Creating UniformDH handshake message.") if self.udh is None: self.udh = obfs3_dh.UniformDH() publicKey = self.udh.get_public() assert (const.MAX_PADDING_LENGTH - const.PUBLIC_KEY_LENGTH) >= 0 # Subtract the length of the public key to make the handshake on # average as long as a redeemed ticket. That should thwart statistical # length-based attacks. padding = mycrypto.strongRandom( random.randint(0, const.MAX_PADDING_LENGTH - const.PUBLIC_KEY_LENGTH)) # Add a mark which enables efficient location of the HMAC. mark = mycrypto.HMAC_SHA256_128(self.sharedSecret, publicKey) # Authenticate the handshake including the current approximate epoch. mac = mycrypto.HMAC_SHA256_128( self.sharedSecret, publicKey + padding + mark + util.getEpoch()) return publicKey + padding + mark + mac
def extractPublicKey( self, data, srvState=None ): """ Extract and return a UniformDH public key out of `data'. Before the public key is touched, the HMAC is verified. If the HMAC is invalid or some other error occurs, `False' is returned. Otherwise, the public key is returned. The extracted data is finally drained from the given `data' object. """ assert self.sharedSecret is not None # Do we already have the minimum amount of data? if len(data) < (const.PUBLIC_KEY_LENGTH + const.MARK_LENGTH + const.HMAC_SHA256_128_LENGTH): return False log.debug("Attempting to extract the remote machine's UniformDH " "public key out of %d bytes of data." % len(data)) handshake = data.peek() # First, find the mark to efficiently locate the HMAC. publicKey = handshake[:const.PUBLIC_KEY_LENGTH] mark = mycrypto.HMAC_SHA256_128(self.sharedSecret, publicKey) index = util.locateMark(mark, handshake) if not index: return False # Now that we know where the authenticating HMAC is: verify it. hmacStart = index + const.MARK_LENGTH existingHMAC = handshake[hmacStart: (hmacStart + const.HMAC_SHA256_128_LENGTH)] myHMAC = mycrypto.HMAC_SHA256_128(self.sharedSecret, handshake[0 : hmacStart] + util.getEpoch()) if not util.isValidHMAC(myHMAC, existingHMAC, self.sharedSecret): log.warning("The HMAC is invalid: `%s' vs. `%s'." % (myHMAC.encode('hex'), existingHMAC.encode('hex'))) return False # Do nothing if the ticket is replayed. Immediately closing the # connection would be suspicious. if srvState is not None and srvState.isReplayed(existingHMAC): log.warning("The HMAC was already present in the replay table.") return False data.drain(index + const.MARK_LENGTH + const.HMAC_SHA256_128_LENGTH) if srvState is not None: log.debug("Adding the HMAC authenticating the UniformDH message " \ "to the replay table: %s." % existingHMAC.encode('hex')) srvState.registerKey(existingHMAC) return handshake[:const.PUBLIC_KEY_LENGTH]
def extractPublicKey(self, data, srvState=None): """ Extract and return a UniformDH public key out of `data'. Before the public key is touched, the HMAC is verified. If the HMAC is invalid or some other error occurs, `False' is returned. Otherwise, the public key is returned. The extracted data is finally drained from the given `data' object. """ assert self.sharedSecret is not None # Do we already have the minimum amount of data? if len(data) < (const.PUBLIC_KEY_LENGTH + const.MARK_LENGTH + const.HMAC_SHA256_128_LENGTH): return False log.debug("Attempting to extract the remote machine's UniformDH " "public key out of %d bytes of data." % len(data)) handshake = data.peek() # First, find the mark to efficiently locate the HMAC. publicKey = handshake[:const.PUBLIC_KEY_LENGTH] mark = mycrypto.HMAC_SHA256_128(self.sharedSecret, publicKey) index = util.locateMark(mark, handshake) if not index: return False # Now that we know where the authenticating HMAC is: verify it. hmacStart = index + const.MARK_LENGTH existingHMAC = handshake[hmacStart:(hmacStart + const.HMAC_SHA256_128_LENGTH)] myHMAC = mycrypto.HMAC_SHA256_128( self.sharedSecret, handshake[0:hmacStart] + util.getEpoch()) if not util.isValidHMAC(myHMAC, existingHMAC, self.sharedSecret): log.warning("The HMAC is invalid: `%s' vs. `%s'." % (myHMAC.encode('hex'), existingHMAC.encode('hex'))) return False # Do nothing if the ticket is replayed. Immediately closing the # connection would be suspicious. if srvState is not None and srvState.isReplayed(existingHMAC): log.warning("The HMAC was already present in the replay table.") return False data.drain(index + const.MARK_LENGTH + const.HMAC_SHA256_128_LENGTH) if srvState is not None: log.debug("Adding the HMAC authenticating the UniformDH message " \ "to the replay table: %s." % existingHMAC.encode('hex')) srvState.registerKey(existingHMAC) return handshake[:const.PUBLIC_KEY_LENGTH]
def createHandshake(self, srvState=None): """ Create and return a ready-to-be-sent UniformDH handshake. The returned handshake data includes the public key, pseudo-random padding, the mark and the HMAC. If a UniformDH object has not been initialised yet, a new instance is created. """ assert self.sharedSecret is not None log.debug("Creating UniformDH handshake message.") if self.udh is None: self.udh = obfs3_dh.UniformDH() publicKey = self.udh.get_public() assert (const.MAX_PADDING_LENGTH - const.PUBLIC_KEY_LENGTH) >= 0 # Subtract the length of the public key to make the handshake on # average as long as a redeemed ticket. That should thwart statistical # length-based attacks. padding = mycrypto.strongRandom( random.randint(0, const.MAX_PADDING_LENGTH - const.PUBLIC_KEY_LENGTH)) # Add a mark which enables efficient location of the HMAC. mark = mycrypto.HMAC_SHA256_128(self.sharedSecret, publicKey) if self.echoEpoch is None: epoch = util.getEpoch() else: epoch = self.echoEpoch log.debug("Echoing epoch rather than recreating it.") # Authenticate the handshake including the current approximate epoch. mac = mycrypto.HMAC_SHA256_128(self.sharedSecret, publicKey + padding + mark + epoch) if self.weAreServer and (srvState is not None): log.debug("Adding the HMAC authenticating the server's UniformDH " "message to the replay table: %s." % mac.encode('hex')) srvState.registerKey(mac) return publicKey + padding + mark + mac
def createHandshake( self, srvState=None ): """ Create and return a ready-to-be-sent UniformDH handshake. The returned handshake data includes the public key, pseudo-random padding, the mark and the HMAC. If a UniformDH object has not been initialised yet, a new instance is created. """ assert self.sharedSecret is not None log.debug("Creating UniformDH handshake message.") if self.udh is None: self.udh = obfs3_dh.UniformDH() publicKey = self.udh.get_public() assert (const.MAX_PADDING_LENGTH - const.PUBLIC_KEY_LENGTH) >= 0 # Subtract the length of the public key to make the handshake on # average as long as a redeemed ticket. That should thwart statistical # length-based attacks. padding = mycrypto.strongRandom(random.randint(0, const.MAX_PADDING_LENGTH - const.PUBLIC_KEY_LENGTH)) # Add a mark which enables efficient location of the HMAC. mark = mycrypto.HMAC_SHA256_128(self.sharedSecret, publicKey) if self.echoEpoch is None: epoch = util.getEpoch() else: epoch = self.echoEpoch log.debug("Echoing epoch rather than recreating it.") # Authenticate the handshake including the current approximate epoch. mac = mycrypto.HMAC_SHA256_128(self.sharedSecret, publicKey + padding + mark + epoch) if self.weAreServer and (srvState is not None): log.debug("Adding the HMAC authenticating the server's UniformDH " "message to the replay table: %s." % mac.encode('hex')) srvState.registerKey(mac) return publicKey + padding + mark + mac
def createTicketMessage(rawTicket, HMACKey): """ Create and return a ready-to-be-sent ticket authentication message. Pseudo-random padding and a mark are added to `rawTicket' and the result is then authenticated using `HMACKey' as key for a HMAC. The resulting authentication message is then returned. """ assert len(rawTicket) == const.TICKET_LENGTH assert len(HMACKey) == const.TICKET_HMAC_KEY_LENGTH # Subtract the length of the ticket to make the handshake on # average as long as a UniformDH handshake message. padding = mycrypto.strongRandom( random.randint(0, const.MAX_PADDING_LENGTH - const.TICKET_LENGTH)) mark = mycrypto.HMAC_SHA256_128(HMACKey, rawTicket) hmac = mycrypto.HMAC_SHA256_128( HMACKey, rawTicket + padding + mark + util.getEpoch()) return rawTicket + padding + mark + hmac
def createTicketMessage( rawTicket, HMACKey ): """ Create and return a ready-to-be-sent ticket authentication message. Pseudo-random padding and a mark are added to `rawTicket' and the result is then authenticated using `HMACKey' as key for a HMAC. The resulting authentication message is then returned. """ assert len(rawTicket) == const.TICKET_LENGTH assert len(HMACKey) == const.TICKET_HMAC_KEY_LENGTH # Subtract the length of the ticket to make the handshake on # average as long as a UniformDH handshake message. padding = mycrypto.strongRandom(random.randint(0, const.MAX_PADDING_LENGTH - const.TICKET_LENGTH)) mark = mycrypto.HMAC_SHA256_128(HMACKey, rawTicket) hmac = mycrypto.HMAC_SHA256_128(HMACKey, rawTicket + padding + mark + util.getEpoch()) return rawTicket + padding + mark + hmac
def receiveTicket( self, data ): """ Extract and verify a potential session ticket. The given `data' is treated as a session ticket. The ticket is being decrypted and authenticated (yes, in that order). If all these steps succeed, `True' is returned. Otherwise, `False' is returned. """ if len(data) < (const.TICKET_LENGTH + const.MARK_LENGTH + const.HMAC_SHA256_128_LENGTH): return False potentialTicket = data.peek() # Now try to decrypt and parse the ticket. We need the master key # inside to verify the HMAC in the next step. if not self.decryptedTicket: newTicket = ticket.decrypt(potentialTicket[:const.TICKET_LENGTH], self.srvState) if newTicket != None and newTicket.isValid(): self.deriveSecrets(newTicket.masterKey) self.decryptedTicket = True else: return False # First, find the mark to efficiently locate the HMAC. mark = mycrypto.HMAC_SHA256_128(self.recvHMAC, potentialTicket[:const.TICKET_LENGTH]) index = util.locateMark(mark, potentialTicket) if not index: return False # Now, verify if the HMAC is valid. existingHMAC = potentialTicket[index + const.MARK_LENGTH: index + const.MARK_LENGTH + const.HMAC_SHA256_128_LENGTH] myHMAC = mycrypto.HMAC_SHA256_128(self.recvHMAC, potentialTicket[0: index + const.MARK_LENGTH] + util.getEpoch()) if not util.isValidHMAC(myHMAC, existingHMAC, self.recvHMAC): log.warning("The HMAC is invalid: `%s' vs. `%s'." % (myHMAC.encode('hex'), existingHMAC.encode('hex'))) return False # Do nothing if the ticket is replayed. Immediately closing the # connection would be suspicious. if self.srvState.isReplayed(existingHMAC): log.warning("The HMAC was already present in the replay table.") return False data.drain(index + const.MARK_LENGTH + const.HMAC_SHA256_128_LENGTH) log.debug("Adding the HMAC authenticating the ticket message to the " \ "replay table: %s." % existingHMAC.encode('hex')) self.srvState.registerKey(existingHMAC) log.debug("Switching to state ST_CONNECTED.") self.protoState = const.ST_CONNECTED return True
def test5_getEpoch( self ): e = util.getEpoch() self.failUnless(isinstance(e, basestring))