def checkKeys(srvState): """ Check whether the key material for session tickets must be rotated. The key material (i.e., AES and HMAC keys for session tickets) contained in `srvState' is checked if it needs to be rotated. If so, the old keys are stored and new ones are created. """ assert (srvState.hmacKey is not None) and \ (srvState.aesKey is not None) and \ (srvState.keyCreation is not None) if (int(time.time()) - srvState.keyCreation) > const.KEY_ROTATION_TIME: log.info("Rotating server key material for session tickets.") # Save expired keys to be able to validate old tickets. srvState.oldAesKey = srvState.aesKey srvState.oldHmacKey = srvState.hmacKey # Create new key material... srvState.aesKey = mycrypto.strongRandom(const.TICKET_AES_KEY_LENGTH) srvState.hmacKey = mycrypto.strongRandom(const.TICKET_HMAC_KEY_LENGTH) srvState.keyCreation = int(time.time()) # ...and save it to disk. srvState.writeState()
def genState( self ): """ Populate all the local variables with values. """ log.info("Generating parameters for the server's state file.") # PRNG seed for the client to reproduce the packet and IAT morpher. self.prngSeed = mycrypto.strongRandom(const.PRNG_SEED_LENGTH) # HMAC and AES key used to encrypt and authenticate tickets. self.hmacKey = mycrypto.strongRandom(const.TICKET_HMAC_KEY_LENGTH) self.aesKey = mycrypto.strongRandom(const.TICKET_AES_KEY_LENGTH) self.keyCreation = int(time.time()) # The previous HMAC and AES keys. self.oldHmacKey = None self.oldAesKey = None # Replay dictionary for both authentication mechanisms. self.replayTracker = replay.Tracker() # Distributions for packet lengths and inter arrival times. prng = random.Random(self.prngSeed) self.pktDist = probdist.new(lambda: prng.randint(const.HDR_LENGTH, const.MTU), seed=self.prngSeed) self.iatDist = probdist.new(lambda: prng.random() % const.MAX_PACKET_DELAY, seed=self.prngSeed) self.writeState()
def checkKeys( srvState ): """ Check whether the key material for session tickets must be rotated. The key material (i.e., AES and HMAC keys for session tickets) contained in `srvState' is checked if it needs to be rotated. If so, the old keys are stored and new ones are created. """ assert (srvState.hmacKey is not None) and \ (srvState.aesKey is not None) and \ (srvState.keyCreation is not None) if (int(time.time()) - srvState.keyCreation) > const.KEY_ROTATION_TIME: log.info("Rotating server key material for session tickets.") # Save expired keys to be able to validate old tickets. srvState.oldAesKey = srvState.aesKey srvState.oldHmacKey = srvState.hmacKey # Create new key material... srvState.aesKey = mycrypto.strongRandom(const.TICKET_AES_KEY_LENGTH) srvState.hmacKey = mycrypto.strongRandom(const.TICKET_HMAC_KEY_LENGTH) srvState.keyCreation = int(time.time()) # ...and save it to disk. srvState.writeState()
def genState(self): """ Populate all the local variables with values. """ log.info("Generating parameters for the server's state file.") # PRNG seed for the client to reproduce the packet and IAT morpher. self.prngSeed = mycrypto.strongRandom(const.PRNG_SEED_LENGTH) # HMAC and AES key used to encrypt and authenticate tickets. self.hmacKey = mycrypto.strongRandom(const.TICKET_HMAC_KEY_LENGTH) self.aesKey = mycrypto.strongRandom(const.TICKET_AES_KEY_LENGTH) self.keyCreation = int(time.time()) # The previous HMAC and AES keys. self.oldHmacKey = None self.oldAesKey = None # Replay dictionary for both authentication mechanisms. self.replayTracker = replay.Tracker() # Distributions for packet lengths and inter arrival times. prng = random.Random(self.prngSeed) self.pktDist = probdist.new( lambda: prng.randint(const.HDR_LENGTH, const.MTU), seed=self.prngSeed) self.iatDist = probdist.new( lambda: prng.random() % const.MAX_PACKET_DELAY, seed=self.prngSeed) # Fallback UniformDH shared secret. Only used if the bridge operator # did not set `ServerTransportOptions'. self.fallbackPassword = os.urandom(const.SHARED_SECRET_LENGTH) self.writeState()
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 genState( self ): """ Populate all the local variables with values. """ log.info("Generating parameters for the server's state file.") # PRNG seed for the client to reproduce the packet and IAT morpher. self.prngSeed = mycrypto.strongRandom(const.PRNG_SEED_LENGTH) # HMAC and AES key used to encrypt and authenticate tickets. self.hmacKey = mycrypto.strongRandom(const.TICKET_HMAC_KEY_LENGTH) self.aesKey = mycrypto.strongRandom(const.TICKET_AES_KEY_LENGTH) self.keyCreation = int(time.time()) # The previous HMAC and AES keys. self.oldHmacKey = None self.oldAesKey = None # Replay dictionary for both authentication mechanisms. self.replayTracker = replay.Tracker() # Distributions for packet lengths and inter arrival times. prng = random.Random(self.prngSeed) self.pktDist = probdist.new(lambda: prng.randint(const.HDR_LENGTH, const.MTU), seed=self.prngSeed) self.iatDist = probdist.new(lambda: prng.random() % const.MAX_PACKET_DELAY, seed=self.prngSeed) # Fallback UniformDH shared secret. Only used if the bridge operator # did not set `ServerTransportOptions'. self.fallbackPassword = os.urandom(const.SHARED_SECRET_LENGTH) # Unauthenticated connections are closed after having received the # following amount of bytes. self.closingThreshold = prng.randint(const.MAX_HANDSHAKE_LENGTH, const.MAX_HANDSHAKE_LENGTH * 5) self.writeState()
def issueTicketAndKey(srvState): """ Issue a new session ticket and append it to the according master key. The parameter `srvState' contains the key material and is passed on to `SessionTicket'. The returned ticket and key are ready to be wrapped into a protocol message with the flag FLAG_NEW_TICKET set. """ log.info("Issuing new session ticket and master key.") masterKey = mycrypto.strongRandom(const.MASTER_KEY_LENGTH) newTicket = (SessionTicket(masterKey, srvState)).issue() return masterKey + newTicket
def issueTicketAndKey( srvState ): """ Issue a new session ticket and append it to the according master key. The parameter `srvState' contains the key material and is passed on to `SessionTicket'. The returned ticket and key are ready to be wrapped into a protocol message with the flag FLAG_NEW_TICKET set. """ log.info("Issuing new session ticket and master key.") masterKey = mycrypto.strongRandom(const.MASTER_KEY_LENGTH) newTicket = (SessionTicket(masterKey, srvState)).issue() return masterKey + newTicket
def __init__(self): # Generate private key self.priv_str = mycrypto.strongRandom(self.group_len) self.priv = int(binascii.hexlify(self.priv_str), 16) # Make the private key even flip = self.priv % 2 self.priv -= flip # Generate public key self.pub = util.powMod(self.g, self.priv, self.mod) if flip == 1: self.pub = self.mod - self.pub self.pub_str = int_to_bytes(self.pub, self.group_len) self.shared_secret = None
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 __init__(self, masterKey, srvState): """ The constructor of the `SessionTicket()' class. The class variables are initialised and the validity of the symmetric keys for the session tickets is checked. """ assert (masterKey is not None) and \ len(masterKey) == const.MASTER_KEY_LENGTH checkKeys(srvState) # Initialisation vector for AES-CBC. self.IV = mycrypto.strongRandom(const.TICKET_AES_CBC_IV_LENGTH) # The server's (encrypted) protocol state. self.state = ProtocolState(masterKey) # AES and HMAC keys to encrypt and authenticate the ticket. self.symmTicketKey = srvState.aesKey self.hmacTicketKey = srvState.hmacKey
def __init__( self, masterKey, srvState ): """ The constructor of the `SessionTicket()' class. The class variables are initialised and the validity of the symmetric keys for the session tickets is checked. """ assert (masterKey is not None) and \ len(masterKey) == const.MASTER_KEY_LENGTH checkKeys(srvState) # Initialisation vector for AES-CBC. self.IV = mycrypto.strongRandom(const.TICKET_AES_CBC_IV_LENGTH) # The server's (encrypted) protocol state. self.state = ProtocolState(masterKey) # AES and HMAC keys to encrypt and authenticate the ticket. self.symmTicketKey = srvState.aesKey self.hmacTicketKey = srvState.hmacKey
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 test4_CSPRNG( self ): self.failIf(mycrypto.strongRandom(10) == mycrypto.strongRandom(10)) self.failIf(len(mycrypto.strongRandom(100)) != 100)
parser = argparse.ArgumentParser() parser.add_argument("ip_addr", type=str, help="The IPv4 address of the " "%s server." % const.TRANSPORT_NAME) parser.add_argument("tcp_port", type=int, help="The TCP port of the %s " "server." % const.TRANSPORT_NAME) parser.add_argument("ticket_file", type=str, help="The file, the newly " "issued ticket is written to.") args = parser.parse_args() print "[+] Loading server state file." serverState = state.load() print "[+] Generating new session ticket." masterKey = mycrypto.strongRandom(const.MASTER_KEY_LENGTH) ticket = SessionTicket(masterKey, serverState).issue() print "[+] Writing new session ticket to `%s'." % args.ticket_file tickets = dict() server = IPv4Address('TCP', args.ip_addr, args.tcp_port) tickets[str(server)] = [int(time.time()), masterKey, ticket] util.writeToFile(yaml.dump(tickets), args.ticket_file) print "[+] Success."
# Give ScrambleSuit server operators a way to manually issue new session # tickets for out-of-band distribution. if __name__ == "__main__": import argparse parser = argparse.ArgumentParser() parser.add_argument("ip_addr", type=str, help="The IPv4 address of the " "%s server." % const.TRANSPORT_NAME) parser.add_argument("tcp_port", type=int, help="The TCP port of the %s " "server." % const.TRANSPORT_NAME) parser.add_argument("ticket_file", type=str, help="The file, the newly " "issued ticket is written to.") args = parser.parse_args() print "[+] Loading server state file." serverState = state.load() print "[+] Generating new session ticket." masterKey = mycrypto.strongRandom(const.MASTER_KEY_LENGTH) ticket = SessionTicket(masterKey, serverState).issue() print "[+] Writing new session ticket to `%s'." % args.ticket_file tickets = dict() server = IPv4Address('TCP', args.ip_addr, args.tcp_port) tickets[str(server)] = [int(time.time()), masterKey, ticket] util.writeToFile(yaml.dump(tickets), args.ticket_file) print "[+] Success."