def test1_isValidHMAC( self ): self.failIf(util.isValidHMAC("A" * const.HMAC_SHA256_128_LENGTH, "B" * const.HMAC_SHA256_128_LENGTH, "X" * const.SHA256_LENGTH) == True) self.failIf(util.isValidHMAC("A" * const.HMAC_SHA256_128_LENGTH, "A" * const.HMAC_SHA256_128_LENGTH, "X" * const.SHA256_LENGTH) == False)
def decrypt(ticket, srvState): """ Decrypts, verifies and returns the given `ticket'. The key material used to verify the ticket is contained in `srvState'. First, the HMAC over the ticket is verified. If it is valid, the ticket is decrypted. Finally, a `ProtocolState()' object containing the master key and the ticket's issue date is returned. If any of these steps fail, `None' is returned. """ assert (ticket is not None) and (len(ticket) == const.TICKET_LENGTH) assert (srvState.hmacKey is not None) and (srvState.aesKey is not None) log.debug("Attempting to decrypt and verify ticket.") checkKeys(srvState) # Verify the ticket's authenticity before decrypting. hmac = HMAC.new(srvState.hmacKey, ticket[0:80], digestmod=SHA256).digest() if util.isValidHMAC(hmac, ticket[80:const.TICKET_LENGTH], srvState.hmacKey): aesKey = srvState.aesKey else: if srvState.oldHmacKey is None: return None # Was the HMAC created using the rotated key material? oldHmac = HMAC.new(srvState.oldHmacKey, ticket[0:80], digestmod=SHA256).digest() if util.isValidHMAC(oldHmac, ticket[80:const.TICKET_LENGTH], srvState.oldHmacKey): aesKey = srvState.oldAesKey else: return None # Decrypt the ticket to extract the state information. aes = AES.new(aesKey, mode=AES.MODE_CBC, IV=ticket[0:const.TICKET_AES_CBC_IV_LENGTH]) plainTicket = aes.decrypt(ticket[const.TICKET_AES_CBC_IV_LENGTH:80]) issueDate = struct.unpack('I', plainTicket[0:4])[0] identifier = plainTicket[4:22] masterKey = plainTicket[22:54] if not (identifier == const.TICKET_IDENTIFIER): log.error("The ticket's HMAC is valid but the identifier is invalid. " "The ticket could be corrupt.") return None return ProtocolState(masterKey, issueDate=issueDate)
def decrypt( ticket, srvState ): """ Decrypts, verifies and returns the given `ticket'. The key material used to verify the ticket is contained in `srvState'. First, the HMAC over the ticket is verified. If it is valid, the ticket is decrypted. Finally, a `ProtocolState()' object containing the master key and the ticket's issue date is returned. If any of these steps fail, `None' is returned. """ assert (ticket is not None) and (len(ticket) == const.TICKET_LENGTH) assert (srvState.hmacKey is not None) and (srvState.aesKey is not None) log.debug("Attempting to decrypt and verify ticket.") checkKeys(srvState) # Verify the ticket's authenticity before decrypting. hmac = HMAC.new(srvState.hmacKey, ticket[0:80], digestmod=SHA256).digest() if util.isValidHMAC(hmac, ticket[80:const.TICKET_LENGTH], srvState.hmacKey): aesKey = srvState.aesKey else: if srvState.oldHmacKey is None: return None # Was the HMAC created using the rotated key material? oldHmac = HMAC.new(srvState.oldHmacKey, ticket[0:80], digestmod=SHA256).digest() if util.isValidHMAC(oldHmac, ticket[80:const.TICKET_LENGTH], srvState.oldHmacKey): aesKey = srvState.oldAesKey else: return None # Decrypt the ticket to extract the state information. aes = AES.new(aesKey, mode=AES.MODE_CBC, IV=ticket[0:const.TICKET_AES_CBC_IV_LENGTH]) plainTicket = aes.decrypt(ticket[const.TICKET_AES_CBC_IV_LENGTH:80]) issueDate = struct.unpack('I', plainTicket[0:4])[0] identifier = plainTicket[4:22] masterKey = plainTicket[22:54] if not (identifier == const.TICKET_IDENTIFIER): log.error("The ticket's HMAC is valid but the identifier is invalid. " "The ticket could be corrupt.") return None return ProtocolState(masterKey, issueDate=issueDate)
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 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] authenticated = False for epoch in util.expandedEpoch(): myHMAC = mycrypto.HMAC_SHA256_128(self.recvHMAC, potentialTicket[0:index + \ const.MARK_LENGTH] + epoch) if util.isValidHMAC(myHMAC, existingHMAC, self.recvHMAC): authenticated = True break log.debug("HMAC invalid. Trying next epoch value.") if not authenticated: log.warning("Could not verify the authentication message's HMAC.") 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 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] authenticated = False for epoch in util.expandedEpoch(): myHMAC = mycrypto.HMAC_SHA256_128(self.recvHMAC, potentialTicket[0:index + \ const.MARK_LENGTH] + epoch) if util.isValidHMAC(myHMAC, existingHMAC, self.recvHMAC): authenticated = True break log.debug("HMAC invalid. Trying next epoch value.") if not authenticated: log.warning("Could not verify the authentication message's HMAC.") 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