def _compute_challenge(self, channel_binding): self._challenge = os.urandom(32) if self._channel_id: self._expected_signed_message = util.xor(self._challenge, self._channel_id) else: self._expected_signed_message = self._challenge extra = {u"challenge": binascii.b2a_hex(self._challenge)} return extra
def sign_challenge(self, session, challenge): """ Sign WAMP-cryptosign challenge. :param challenge: The WAMP-cryptosign challenge object for which a signature should be computed. :type challenge: instance of autobahn.wamp.types.Challenge :returns: A Deferred/Future that resolves to the computed signature. :rtype: str """ if not isinstance(challenge, Challenge): raise Exception("challenge must be instance of autobahn.wamp.types.Challenge, not {}".format(type(challenge))) if u'challenge' not in challenge.extra: raise Exception("missing challenge value in challenge.extra") # the challenge sent by the router (a 32 bytes random value) challenge_hex = challenge.extra[u'challenge'] # the challenge for WAMP-cryptosign is a 32 bytes random value in Hex encoding (that is, a unicode string) challenge_raw = binascii.a2b_hex(challenge_hex) # if the transport has a channel ID, the message to be signed by the client actually # is the XOR of the challenge and the channel ID channel_id_raw = session._transport.get_channel_id() if channel_id_raw: data = util.xor(challenge_raw, channel_id_raw) else: data = challenge_raw # a raw byte string is signed, and the signature is also a raw byte string d1 = self.sign(data) # asyncio lacks callback chaining (and we cannot use co-routines, since we want # to support older Pythons), hence we need d2 d2 = txaio.create_future() def process(signature_raw): # convert the raw signature into a hex encode value (unicode string) signature_hex = binascii.b2a_hex(signature_raw).decode('ascii') # we return the concatenation of the signature and the message signed (96 bytes) data_hex = binascii.b2a_hex(data).decode('ascii') sig = signature_hex + data_hex txaio.resolve(d2, sig) txaio.add_callbacks(d1, process, None) return d2
def sign_challenge(self, session, challenge): """ Sign WAMP-cryptosign challenge. :param challenge: The WAMP-cryptosign challenge object for which a signature should be computed. :type challenge: instance of autobahn.wamp.types.Challenge :returns: A Deferred/Future that resolves to the computed signature. :rtype: unicode """ if not isinstance(challenge, Challenge): raise Exception( "challenge must be instance of autobahn.wamp.types.Challenge, not {}" .format(type(challenge))) if u'challenge' not in challenge.extra: raise Exception("missing challenge value in challenge.extra") # the challenge sent by the router (a 32 bytes random value) challenge_hex = challenge.extra[u'challenge'] # the challenge for WAMP-cryptosign is a 32 bytes random value in Hex encoding (that is, a unicode string) challenge_raw = binascii.a2b_hex(challenge_hex) # if the transport has a channel ID, the message to be signed by the client actually # is the XOR of the challenge and the channel ID channel_id_raw = session._transport.get_channel_id() if channel_id_raw: data = util.xor(challenge_raw, channel_id_raw) else: data = challenge_raw # a raw byte string is signed, and the signature is also a raw byte string signature_raw = yield self.sign(data) # convert the raw signature into a hex encode value (unicode string) signature_hex = binascii.b2a_hex(signature_raw).decode('ascii') # we return the concatenation of the signature and the message signed (96 bytes) data_hex = binascii.b2a_hex(data).decode('ascii') # we always return a future/deferred, so handling is uniform returnValue(signature_hex + data_hex)
def format_challenge(challenge: Challenge, channel_id_raw: bytes, channel_id_type: str) -> bytes: """ Format the challenge based on provided parameters :param challenge: The WAMP-cryptosign challenge object for which a signature should be computed. :param channel_id_raw: The channel ID when channel_id_type is 'tls-unique'. :param channel_id_type: The type of the channel id, currently handles 'tls-unique' and ignores otherwise. """ if not isinstance(challenge, Challenge): raise Exception( "challenge must be instance of autobahn.wamp.types.Challenge, not {}".format(type(challenge))) if 'challenge' not in challenge.extra: raise Exception("missing challenge value in challenge.extra") # the challenge sent by the router (a 32 bytes random value) challenge_hex = challenge.extra['challenge'] if type(challenge_hex) != str: raise Exception("invalid type {} for challenge (expected a hex string)".format(type(challenge_hex))) if len(challenge_hex) != 64: raise Exception("unexpected challenge (hex) length: was {}, but expected 64".format(len(challenge_hex))) # the challenge for WAMP-cryptosign is a 32 bytes random value in Hex encoding (that is, a unicode string) challenge_raw = binascii.a2b_hex(challenge_hex) if channel_id_type == 'tls-unique': assert len( channel_id_raw) == 32, 'unexpected TLS transport channel ID length (was {}, but expected 32)'.format( len(channel_id_raw)) # with TLS channel binding of type "tls-unique", the message to be signed by the client actually # is the XOR of the challenge and the TLS channel ID data = util.xor(challenge_raw, channel_id_raw) elif channel_id_type is None: # when no channel binding was requested, the message to be signed by the client is the challenge only data = challenge_raw else: assert False, 'invalid channel_id_type "{}"'.format(channel_id_type) return data
def sign_challenge(self, session, challenge): """ Sign WAMP-cryptosign challenge. :param challenge: The WAMP-cryptosign challenge object for which a signature should be computed. :type challenge: instance of autobahn.wamp.types.Challenge :returns: A Deferred/Future that resolves to the computed signature. :rtype: unicode """ if not isinstance(challenge, Challenge): raise Exception("challenge must be instance of autobahn.wamp.types.Challenge, not {}".format(type(challenge))) if u'challenge' not in challenge.extra: raise Exception("missing challenge value in challenge.extra") # the challenge sent by the router (a 32 bytes random value) challenge_hex = challenge.extra[u'challenge'] # the challenge for WAMP-cryptosign is a 32 bytes random value in Hex encoding (that is, a unicode string) challenge_raw = binascii.a2b_hex(challenge_hex) # if the transport has a channel ID, the message to be signed by the client actually # is the XOR of the challenge and the channel ID channel_id_raw = session._transport.get_channel_id() if channel_id_raw: data = util.xor(challenge_raw, channel_id_raw) else: data = challenge_raw # a raw byte string is signed, and the signature is also a raw byte string signature_raw = yield self.sign(data) # convert the raw signature into a hex encode value (unicode string) signature_hex = binascii.b2a_hex(signature_raw).decode('ascii') # we return the concatenation of the signature and the message signed (96 bytes) data_hex = binascii.b2a_hex(data).decode('ascii') # we always return a future/deferred, so handling is uniform returnValue(signature_hex + data_hex)
def authenticate(self, signed_message): """ Verify the signed message sent by the client. :param signed_message: the base64-encoded result "ClientProof" from the SCRAM protocol """ channel_binding = "" client_nonce = base64.b64encode(self._client_nonce).decode('ascii') server_nonce = base64.b64encode(self._server_nonce).decode('ascii') salt = base64.b64encode(self._salt).decode('ascii') auth_message = ( "{client_first_bare},{server_first},{client_final_no_proof}".format( client_first_bare="n={},r={}".format(saslprep(self._authid), client_nonce), server_first="r={},s={},i={}".format(server_nonce, salt, self._iterations), client_final_no_proof="c={},r={}".format(channel_binding, server_nonce), ) ) received_client_proof = base64.b64decode(signed_message) client_signature = hmac.new(self._stored_key, auth_message.encode('ascii'), hashlib.sha256).digest() recovered_client_key = util.xor(client_signature, received_client_proof) recovered_stored_key = hashlib.new('sha256', recovered_client_key).digest() # if we adjust self._authextra before _accept() it gets sent # back to the client server_signature = hmac.new(self._server_key, auth_message.encode('ascii'), hashlib.sha256).digest() if self._authextra is None: self._authextra = {} self._authextra['scram_server_signature'] = base64.b64encode(server_signature).decode('ascii') if hmac.compare_digest(recovered_stored_key, self._stored_key): return self._accept() self.log.error("SCRAM authentication failed for '{authid}'", authid=self._authid) return types.Deny(message=u'SCRAM authentication failed')
def authenticate(self, signed_message): """ Verify the signed message sent by the client. :param signed_message: the base64-encoded result "ClientProof" from the SCRAM protocol """ channel_binding = "" client_nonce = base64.b64encode(self._client_nonce).decode('ascii') server_nonce = base64.b64encode(self._server_nonce).decode('ascii') salt = base64.b64encode(self._salt).decode('ascii') auth_message = ( "{client_first_bare},{server_first},{client_final_no_proof}".format( client_first_bare="n={},r={}".format(saslprep(self._authid), client_nonce), server_first="r={},s={},i={}".format(server_nonce, salt, self._iterations), client_final_no_proof="c={},r={}".format(channel_binding, server_nonce), ) ) received_client_proof = base64.b64decode(signed_message) client_signature = hmac.new(self._stored_key, auth_message.encode('ascii'), hashlib.sha256).digest() recovered_client_key = util.xor(client_signature, received_client_proof) recovered_stored_key = hashlib.new('sha256', recovered_client_key).digest() # if we adjust self._authextra before _accept() it gets sent # back to the client server_signature = hmac.new(self._server_key, auth_message.encode('ascii'), hashlib.sha256).digest() if self._authextra is None: self._authextra = {} self._authextra['scram_server_signature'] = base64.b64encode(server_signature).decode('ascii') if hmac.compare_digest(recovered_stored_key, self._stored_key): return self._accept() self.log.error("SCRAM authentication failed for '{authid}'", authid=self._authid) return types.Deny(message='SCRAM authentication failed')
def sign_challenge(self, session, challenge): """ Sign WAMP-cryptosign challenge. :param session: The authenticating WAMP session. :type session: :class:`autobahn.wamp.protocol.ApplicationSession` :param challenge: The WAMP-cryptosign challenge object for which a signature should be computed. :type challenge: instance of autobahn.wamp.types.Challenge :returns: A Deferred/Future that resolves to the computed signature. :rtype: str """ if not isinstance(challenge, Challenge): raise Exception( u"challenge must be instance of autobahn.wamp.types.Challenge, not {}" .format(type(challenge))) if u'challenge' not in challenge.extra: raise Exception(u"missing challenge value in challenge.extra") # the challenge sent by the router (a 32 bytes random value) challenge_hex = challenge.extra[u'challenge'] if type(challenge_hex) != six.text_type: raise Exception( u"invalid type {} for challenge (expected a hex string)". format(type(challenge_hex))) if len(challenge_hex) != 64: raise Exception( u"unexpected challenge (hex) length: was {}, but expected 64" .format(len(challenge_hex))) # the challenge for WAMP-cryptosign is a 32 bytes random value in Hex encoding (that is, a unicode string) challenge_raw = binascii.a2b_hex(challenge_hex) # if the transport has a channel ID, the message to be signed by the client actually # is the XOR of the challenge and the channel ID channel_id_raw = session._transport.get_channel_id() if channel_id_raw: assert len( channel_id_raw ) == 32, 'unexpected TLS transport channel ID length: was {}, but expected 32'.format( len(channel_id_raw)) data = util.xor(challenge_raw, channel_id_raw) else: data = challenge_raw # a raw byte string is signed, and the signature is also a raw byte string d1 = self.sign(data) # asyncio lacks callback chaining (and we cannot use co-routines, since we want # to support older Pythons), hence we need d2 d2 = txaio.create_future() def process(signature_raw): # convert the raw signature into a hex encode value (unicode string) signature_hex = binascii.b2a_hex(signature_raw).decode('ascii') # we return the concatenation of the signature and the message signed (96 bytes) data_hex = binascii.b2a_hex(data).decode('ascii') sig = signature_hex + data_hex txaio.resolve(d2, sig) txaio.add_callbacks(d1, process, None) return d2
def xor(bin1, bin2): return util.xor(bin1, bin2)