def testWireFormatAccess(self): m = rdf_flows.SignedMessageList() now = 1369308998000000 # An unset RDFDatetime with no defaults will be None. self.assertEqual(m.timestamp, None) # Set the wireformat to the integer equivalent. m.SetPrimitive("timestamp", now) self.assertTrue(isinstance(m.timestamp, rdfvalue.RDFDatetime)) self.assertEqual(m.timestamp, now) rdf_now = rdfvalue.RDFDatetime().Now() m.timestamp = rdf_now self.assertEqual(m.GetPrimitive("timestamp"), int(rdf_now))
def DecodeMessages(self, response_comms): """Extract and verify server message. Args: response_comms: A ClientCommunication rdfvalue Returns: list of messages and the CN where they came from. Raises: DecryptionError: If the message failed to decrypt properly. """ if response_comms.api_version not in [3]: raise DecryptionError("Unsupported api version: %s, expected 3." % response_comms.api_version) if response_comms.encrypted_cipher: # Have we seen this cipher before? try: cipher = self.encrypted_cipher_cache.Get( response_comms.encrypted_cipher) except KeyError: cipher = ReceivedCipher(response_comms, self.private_key, self.pub_key_cache) if cipher.signature_verified: # Remember it for next time. self.encrypted_cipher_cache.Put( response_comms.encrypted_cipher, cipher) # Verify the cipher HMAC with the new response_comms. This will raise # DecryptionError if the HMAC does not agree. cipher.VerifyHMAC(response_comms) # Decrypt the message with the per packet IV. plain = cipher.Decrypt(response_comms.encrypted, response_comms.packet_iv) try: signed_message_list = rdf_flows.SignedMessageList(plain) except rdfvalue.DecodeError as e: raise DecryptionError(str(e)) message_list = self.DecompressMessageList(signed_message_list) else: # The message is not encrypted. We do not allow unencrypted # messages: raise DecryptionError("Server response is not encrypted.") # Are these messages authenticated? auth_state = self.VerifyMessageSignature(response_comms, signed_message_list, cipher, response_comms.api_version) # Mark messages as authenticated and where they came from. for msg in message_list.job: msg.auth_state = auth_state msg.source = cipher.cipher_metadata.source return (message_list.job, cipher.cipher_metadata.source, signed_message_list.timestamp)
def EncodeMessages(self, message_list, result, destination=None, timestamp=None, api_version=3): """Accepts a list of messages and encodes for transmission. This function signs and then encrypts the payload. Args: message_list: A MessageList rdfvalue containing a list of GrrMessages. result: A ClientCommunication rdfvalue which will be filled in. destination: The CN of the remote system this should go to. timestamp: A timestamp to use for the signed messages. If None - use the current time. api_version: The api version which this should be encoded in. Returns: A nonce (based on time) which is inserted to the encrypted payload. The client can verify that the server is able to decrypt the message and return the nonce. Raises: RuntimeError: If we do not support this api version. """ if api_version not in [3]: raise RuntimeError( "Unsupported api version: %s, expected 3." % api_version) # TODO(user): This is actually not great, we have two # communicator classes already, one for the client, one for the # server. This should be different methods, not a single one that # gets passed a destination (server side) or not (client side). if destination is None: destination = self.server_name # For the client it makes sense to cache the server cipher since # it's the only cipher it ever uses. cipher = self._GetServerCipher() else: remote_public_key = self._GetRemotePublicKey(destination) cipher = Cipher(self.common_name, self.private_key, remote_public_key) # Make a nonce for this transaction if timestamp is None: self.timestamp = timestamp = long(time.time() * 1000000) signed_message_list = rdf_flows.SignedMessageList(timestamp=timestamp) self.EncodeMessageList(message_list, signed_message_list) result.encrypted_cipher_metadata = cipher.encrypted_cipher_metadata # Include the encrypted cipher. result.encrypted_cipher = cipher.encrypted_cipher serialized_message_list = signed_message_list.SerializeToString() # Encrypt the message symmetrically. # New scheme cipher is signed plus hmac over message list. result.packet_iv, result.encrypted = cipher.Encrypt(serialized_message_list) # This is to support older endpoints. result.hmac = cipher.HMAC(result.encrypted) # Newer endpoints only look at this HMAC. It is recalculated for each packet # in the session. Note that encrypted_cipher and encrypted_cipher_metadata # do not change between all packets in this session. result.full_hmac = cipher.HMAC(result.encrypted, result.encrypted_cipher, result.encrypted_cipher_metadata, result.packet_iv.SerializeToString(), struct.pack("<I", api_version)) result.api_version = api_version if isinstance(result, rdfvalue.RDFValue): # Store the number of messages contained. result.num_messages = len(message_list) return timestamp
def EncodeMessages(self, message_list, result, destination=None, timestamp=None, api_version=3): """Accepts a list of messages and encodes for transmission. This function signs and then encrypts the payload. Args: message_list: A MessageList rdfvalue containing a list of GrrMessages. result: A ClientCommunication rdfvalue which will be filled in. destination: The CN of the remote system this should go to. timestamp: A timestamp to use for the signed messages. If None - use the current time. api_version: The api version which this should be encoded in. Returns: A nonce (based on time) which is inserted to the encrypted payload. The client can verify that the server is able to decrypt the message and return the nonce. Raises: RuntimeError: If we do not support this api version. """ if api_version not in [3]: raise RuntimeError("Unsupported api version: %s, expected 3." % api_version) if destination is None: destination = self.server_name # Make a nonce for this transaction if timestamp is None: self.timestamp = timestamp = long(time.time() * 1000000) # Do we have a cached cipher to talk to this destination? try: cipher = self.cipher_cache.Get(destination) except KeyError: # Make a new one cipher = Cipher(self.common_name, destination, self.private_key, self.pub_key_cache) self.cipher_cache.Put(destination, cipher) signed_message_list = rdf_flows.SignedMessageList(timestamp=timestamp) self.EncodeMessageList(message_list, signed_message_list) result.encrypted_cipher_metadata = cipher.encrypted_cipher_metadata # Include the encrypted cipher. result.encrypted_cipher = cipher.encrypted_cipher serialized_message_list = signed_message_list.SerializeToString() # Encrypt the message symmetrically. # New scheme cipher is signed plus hmac over message list. result.packet_iv, result.encrypted = cipher.Encrypt( serialized_message_list) # This is to support older endpoints. result.hmac = cipher.HMAC(result.encrypted) # Newer endpoints only look at this HMAC. It is recalculated for each packet # in the session. Note that encrypted_cipher and encrypted_cipher_metadata # do not change between all packets in this session. result.full_hmac = cipher.HMAC(result.encrypted, result.encrypted_cipher, result.encrypted_cipher_metadata, result.packet_iv, struct.pack("<I", api_version)) result.api_version = api_version if isinstance(result, rdfvalue.RDFValue): # Store the number of messages contained. result.num_messages = len(message_list) return timestamp
def DecodeMessages(self, response_comms): """Extract and verify server message. Args: response_comms: A ClientCommunication rdfvalue Returns: list of messages and the CN where they came from. Raises: DecryptionError: If the message failed to decrypt properly. """ # Have we seen this cipher before? cipher_verified = False try: cipher = self.encrypted_cipher_cache.Get( response_comms.encrypted_cipher) # Even though we have seen this encrypted cipher already, we should still # make sure that all the other fields are sane and verify the HMAC. cipher.VerifyReceivedHMAC(response_comms) cipher_verified = True # If we have the cipher in the cache, we know the source and # should have a corresponding public key. source = cipher.GetSource() remote_public_key = self._GetRemotePublicKey(source) except KeyError: cipher = ReceivedCipher(response_comms, self.private_key) source = cipher.GetSource() try: remote_public_key = self._GetRemotePublicKey(source) if cipher.VerifyCipherSignature(remote_public_key): # At this point we know this cipher is legit, we can cache it. self.encrypted_cipher_cache.Put( response_comms.encrypted_cipher, cipher) cipher_verified = True except UnknownClientCert: # We don't know who we are talking to. remote_public_key = None # Decrypt the message with the per packet IV. plain = cipher.Decrypt(response_comms.encrypted, response_comms.packet_iv) try: signed_message_list = rdf_flows.SignedMessageList(plain) except rdfvalue.DecodeError as e: raise DecryptionError(str(e)) message_list = self.DecompressMessageList(signed_message_list) # Are these messages authenticated? auth_state = self.VerifyMessageSignature(response_comms, signed_message_list, cipher, cipher_verified, response_comms.api_version, remote_public_key) # Mark messages as authenticated and where they came from. for msg in message_list.job: msg.auth_state = auth_state msg.source = cipher.cipher_metadata.source return (message_list.job, cipher.cipher_metadata.source, signed_message_list.timestamp)