def validate_token(self, token): buf = ssh.base64url_decode(token) hmac_token = protocol.VerifiablePayload.deserialize(buf) t = protocol.Token.deserialize(hmac_token.payload) if t.valid_to < self.now_func(): s = "Token expired at " + time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime(t.valid_to)) raise exceptions.TokenExpiredException(s) if t.valid_from > self.now_func(): s = time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime(t.valid_from)) raise exceptions.TokenExpiredException("Token created at %s" % s) if (t.valid_to - t.valid_from) > MAX_TOKEN_LIFETIME: raise exceptions.InvalidInputException("Token lifetime too long") if not hmac_token.verify(self._hmac): raise exceptions.InvalidInputException("Token hmac verification " "failed, not matching our " "secret") return t.username
def create_response(challenge, server_name, signer_plug=None, packing=xdr_packing): """Called by a client with the challenge provided by the server to generate a response using the local ssh-agent""" b = ssh.base64url_decode(challenge) hmac_challenge = protocol.VerifiablePayload.deserialize(packing, b) challenge = protocol.Challenge.deserialize(packing, hmac_challenge.payload) if challenge.server_name != server_name: s = "Possible MITM attack. Challenge originates from '%s' " "and not '%s'" % ( challenge.server_name, server_name, ) raise exceptions.InvalidInputException(s) if not signer_plug: signer_plug = ssh.AgentSigner() challenge = protocol.Challenge.deserialize(packing, hmac_challenge.payload) signature = signer_plug.sign(challenge.serialize(packing)) signer_plug.close() response = protocol.Response(signature=signature, hmac_challenge=hmac_challenge) return ssh.base64url_encode(response.serialize(packing))
def test_create_challenge_v1(self): auth_server = server.AuthServer("secret", DummyKeyProvider(), "server.name") challenge = auth_server.create_challenge("noa", 1) cb = ssh.base64url_decode(challenge) decoded_challenge = msgpack_protocol.Challenge.deserialize(cb) self.assertEquals("\xfb\xa1\xeao\xd3y", decoded_challenge.fingerprint)
def test_create_challenge(self): auth_server = server.AuthServer("gurka", DummyKeyProvider(), "server_name") s = auth_server.create_challenge("noa") cb = ssh.base64url_decode(s) verifiable_payload = protocol.VerifiablePayload.deserialize(cb) challenge = protocol.Challenge.deserialize(verifiable_payload.payload) self.assertEquals("\xfb\xa1\xeao\xd3y", challenge.fingerprint)
def test_create_challenge(self): auth_server = server.AuthServer("gurka", DummyKeyProvider(), "server.name") s = auth_server.create_challenge("noa") cb = ssh.base64url_decode(s) verifiable_payload = protocol.VerifiablePayload.deserialize(cb) challenge = protocol.Challenge.deserialize(verifiable_payload.payload) self.assertEquals("\xfb\xa1\xeao\xd3y", challenge.fingerprint)
def create_token(self, response): """ This method verifies that the response given from the client is valid and if so returns a token used for authentication. """ try: s = ssh.base64url_decode(response) except exceptions.CrtAuthError as why: raise exceptions.InvalidInputException(why) r = protocol.Response.deserialize(s) if not r.hmac_challenge.verify(self._hmac): s = "Challenge hmac verification failed, not matching our secret" raise exceptions.InvalidInputException(s) # verify the integrity of the challenge in the response challenge = protocol.Challenge.deserialize(r.hmac_challenge.payload) if self.server_name != challenge.server_name: s = "Got challenge with the wrong server_name encoded" raise exceptions.InvalidInputException(s) key = self.key_provider.get_key(challenge.username) if not key.verify_signature(r.signature, r.hmac_challenge.payload): raise exceptions.InvalidInputException("Client did not provide " "proof that it controls " "the secret key") if challenge.valid_from > self.now_func(): s = time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime(challenge.valid_from)) raise exceptions.InvalidInputException("Response with challenge " "created as %s too new " % s) if challenge.valid_to < self.now_func(): s = time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime(challenge.valid_from)) raise exceptions.InvalidInputException("Response with challenge " "created as %s too old " % s) expire_time = int(self.now_func()) + self.token_lifetime return self._make_token(challenge.username, expire_time)
def parse_request(request): """ This method contains logic to detect a v1 and beyond request and differentiate it from a version 0 request, which is just an ascii username. While all v1 requests are also valid usernames (the curse and blessing of base64 encoding) it is pretty unlikely that a username happens to also decode to a valid msgpack message with the correct magic values. @return a tuple containing username then version """ if len(request) % 4 == 1: # strings of length 1, 5, 9.. has invalid base64 padding, so they # must be ascii usernames. return request, 0 binary = ssh.base64url_decode(request) if len(binary) < 4: return request, 0 if ord(binary[0]) > 4 or binary[1] != 'q': # This code handles version values up to 4. Should give plenty # of time to forget all about the unversioned version 0 return request, 0 b = ord(binary[2]) if (b < 0xa1 or b > 0xbf) and b != 0xd9: # third byte does not indicate a string longer than 0 and shorter # than 256 octets long (According to UTF_8 rfc3629, a unicode # char can encode to at most 4 UTF-8 bytes, and username values # in crtauth is limited to 64 characters, thus the max number of # bytes a username can be is 64 * 4 == 256 return request, 0 if b == 0xd9: username_start = 4 username_len = ord(binary[3]) else: username_start = 3 username_len = ord(binary[2]) & 0x1f if len(binary) - username_start < username_len: # decoded string is too short return request, 0 return binary[username_start:username_start + username_len], 1
def create_response(challenge, server_name, signer_plug=None): """Called by a client with the challenge provided by the server to generate a response using the local ssh-agent""" b = ssh.base64url_decode(challenge) if b[0] == 'v': # this is version 0 challenge hmac_challenge = protocol.VerifiablePayload.deserialize(b) challenge = protocol.Challenge.deserialize(hmac_challenge.payload) to_sign = hmac_challenge.payload version_1 = False elif b[0] == '\x01': # version 1 challenge = msgpack_protocol.Challenge.deserialize(b) to_sign = b version_1 = True else: raise exceptions.ProtocolError("invalid first byte of challenge") if challenge.server_name != server_name: s = ("Possible MITM attack. Challenge originates from '%s' " "and not '%s'" % (challenge.server_name, server_name)) raise exceptions.InvalidInputException(s) if not signer_plug: signer_plug = ssh.AgentSigner() signature = signer_plug.sign(to_sign, challenge.fingerprint) signer_plug.close() if version_1: response = msgpack_protocol.Response(challenge=b, signature=signature) else: response = protocol.Response( signature=signature, hmac_challenge=hmac_challenge) return ssh.base64url_encode(response.serialize())
def create_response(challenge, server_name, signer_plug=None): """Called by a client with the challenge provided by the server to generate a response using the local ssh-agent""" b = ssh.base64url_decode(challenge) if b[0] == 'v': # this is version 0 challenge hmac_challenge = protocol.VerifiablePayload.deserialize(b) challenge = protocol.Challenge.deserialize(hmac_challenge.payload) to_sign = hmac_challenge.payload version_1 = False elif b[0] == '\x01': # version 1 challenge = msgpack_protocol.Challenge.deserialize(b) to_sign = b version_1 = True else: raise exceptions.ProtocolError("invalid first byte of challenge") if challenge.server_name != server_name: s = ("Possible MITM attack. Challenge originates from '%s' " "and not '%s'" % (challenge.server_name, server_name)) raise exceptions.InvalidInputException(s) if not signer_plug: signer_plug = ssh.AgentSigner() signature = signer_plug.sign(to_sign, challenge.fingerprint) signer_plug.close() if version_1: response = msgpack_protocol.Response(challenge=b, signature=signature) else: response = protocol.Response(signature=signature, hmac_challenge=hmac_challenge) return ssh.base64url_encode(response.serialize())
def create_token(self, response): """ This method verifies that the response given from the client is valid and if so returns a token used for authentication. """ s = ssh.base64url_decode(response) if s[0] == 'r': # this is a version 0 response version_1 = False if self.lowest_supported_version > 0: raise exceptions.ProtocolVersionError( "Client needs to support at least version %d" % self.lowest_supported_version) r = protocol.Response.deserialize(s) if not r.hmac_challenge.verify(self._hmac): raise exceptions.InvalidInputException( "Challenge hmac verification failed, not matching secret") challenge = protocol.Challenge.deserialize( r.hmac_challenge.payload) elif s[0] == '\x01': # this is a version 1 response version_1 = True r = msgpack_protocol.Response.deserialize(s) challenge = msgpack_protocol.Challenge.deserialize_authenticated( r.challenge, self.secret) else: raise exceptions.ProtocolError("invalid first byte of response") # verify the integrity of the challenge in the response if self.server_name != challenge.server_name: s = "Got challenge with the wrong server_name encoded" raise exceptions.InvalidInputException(s) key = self.key_provider.get_key(challenge.username) if version_1: if not key.verify_signature(r.signature, r.challenge): raise exceptions.InvalidInputException( "Client did not provide proof that it controls " "the secret key") else: if not key.verify_signature(r.signature, r.hmac_challenge.payload): raise exceptions.InvalidInputException( "Client did not provide proof that it controls " "the secret key") if challenge.valid_from > self.now_func(): s = time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime(challenge.valid_from)) raise exceptions.InvalidInputException("Response with challenge " "created as %s too new " % s) if challenge.valid_to < self.now_func(): s = time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime(challenge.valid_from)) raise exceptions.InvalidInputException("Response with challenge " "created as %s too old " % s) expire_time = int(self.now_func()) + self.token_lifetime return self._make_token(challenge.username, expire_time)
def test_read_binary_key(self): key = rsa.RSAPublicKey(ssh.base64url_decode(s.split(" ")[1])) self.assertEqual(key.fingerprint(), "\xfb\xa1\xeao\xd3y") self.assertEqual(key.decoded, inner_s) self.assertEqual(key.encoded[:15], "\x00\x00\x00\x07ssh-rsa" "\x00\x00\x00\x01")
def test_b64_roundtrip(self): l = ["a", "ab", "abc", "abcd"] for i in l: self.assertEquals(ssh.base64url_decode(ssh.base64url_encode(i)), i)
def create_token(self, response): """ This method verifies that the response given from the client is valid and if so returns a token used for authentication. """ s = ssh.base64url_decode(response) if s[0] == 'r': # this is a version 0 response version_1 = False if self.lowest_supported_version > 0: raise exceptions.ProtocolVersionError( "Client needs to support at least version %d" % self.lowest_supported_version ) r = protocol.Response.deserialize(s) if not r.hmac_challenge.verify(self._hmac): raise exceptions.InvalidInputException( "Challenge hmac verification failed, not matching secret" ) challenge = protocol.Challenge.deserialize(r.hmac_challenge.payload) elif s[0] == '\x01': # this is a version 1 response version_1 = True r = msgpack_protocol.Response.deserialize(s) challenge = msgpack_protocol.Challenge.deserialize_authenticated( r.challenge, self.secret) else: raise exceptions.ProtocolError("invalid first byte of response") # verify the integrity of the challenge in the response if self.server_name != challenge.server_name: s = "Got challenge with the wrong server_name encoded" raise exceptions.InvalidInputException(s) key = self.key_provider.get_key(challenge.username) if challenge.valid_from > self.now_func(): s = time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime(challenge.valid_from)) raise exceptions.InvalidInputException("Response with challenge " "created as %s too new " % s) if challenge.valid_to < self.now_func(): s = time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime(challenge.valid_from)) raise exceptions.InvalidInputException("Response with challenge " "created as %s too old " % s) if version_1: if not key.verify_signature(r.signature, r.challenge): raise exceptions.InvalidInputException( "Client did not provide proof that it controls " "the secret key") else: if not key.verify_signature(r.signature, r.hmac_challenge.payload): raise exceptions.InvalidInputException( "Client did not provide proof that it controls " "the secret key") expire_time = int(self.now_func()) + self.token_lifetime return self._make_token(challenge.username, expire_time)