def readLink(self, path): """ Find the root of a set of symbolic links. This method returns the target of the link, or a Deferred that returns the same. @type path: L{bytes} @param path: the path of the symlink to read. """ d = self._sendRequest(FXP_READLINK, NS(path)) return d.addCallback(self._cbRealPath)
def test_failedAuthentication(self): """ When provided with invalid authentication details, the server should respond by sending a MSG_USERAUTH_FAILURE message which states whether the authentication was partially successful, and provides other, open options for authentication. See RFC 4252, Section 5.1. """ # packet = username, next_service, authentication type, FALSE, password packet = NS('foo') + NS('none') + NS('password') + chr(0) + NS('bar') d = self.authServer.ssh_USERAUTH_REQUEST(packet) def check(ignored): # Check that the server reports the failure, including 'password' # as a valid authentication type. self.assertEqual( self.authServer.transport.packets, [(userauth.MSG_USERAUTH_FAILURE, NS('password') + chr(0))]) return d.addCallback(check)
def _ebMaybeBadAuth(self, reason): """ An intermediate errback. If the reason is error.NotEnoughAuthentication, we send a MSG_USERAUTH_FAILURE, but with the partial success indicator set. @type reason: L{twisted.python.failure.Failure} """ reason.trap(error.NotEnoughAuthentication) self.transport.sendPacket( MSG_USERAUTH_FAILURE, NS(b','.join(self.supportedAuthentications)) + b'\xff')
def test_old_publickey_getPublicKey(self): """ Old SSHUserAuthClients returned strings of public key blobs from getPublicKey(). Test that a Deprecation warning is raised but the key is verified correctly. """ oldAuth = OldClientAuth('foo', FakeTransport.Service()) oldAuth.transport = FakeTransport(None) oldAuth.transport.sessionID = 'test' oldAuth.serviceStarted() oldAuth.transport.packets = [] self.assertWarns( DeprecationWarning, "Returning a string from " "SSHUserAuthClient.getPublicKey() is deprecated since " "Twisted 9.0. Return a keys.Key() instead.", userauth.__file__, oldAuth.tryAuth, 'publickey') self.assertEquals( oldAuth.transport.packets, [(userauth.MSG_USERAUTH_REQUEST, NS('foo') + NS('nancy') + NS('publickey') + '\x00' + NS('ssh-rsa') + NS(keys.Key.fromString(keydata.publicRSA_openssh).blob()))])
def removeDirectory(self, path): """ Remove a directory (non-recursively) It is an error to remove a directory that has files or directories in it. This method returns a Deferred that is called back when it is removed. @param path: the directory to remove. """ return self._sendRequest(FXP_RMDIR, NS(path))
def test_failedPasswordAuthentication(self): """ When provided with invalid authentication details, the server should respond by sending a MSG_USERAUTH_FAILURE message which states whether the authentication was partially successful, and provides other, open options for authentication. See RFC 4252, Section 5.1. """ # packet = username, next_service, authentication type, FALSE, password packet = b''.join( [NS(b'foo'), NS(b'none'), NS(b'password'), chr(0), NS(b'bar')]) self.authServer.clock = task.Clock() d = self.authServer.ssh_USERAUTH_REQUEST(packet) self.assertEqual(self.authServer.transport.packets, []) self.authServer.clock.advance(2) return d.addCallback(self._checkFailed)
def test_successfulPasswordAuthentication(self): """ When provided with correct password authentication information, the server should respond by sending a MSG_USERAUTH_SUCCESS message with no other data. See RFC 4252, Section 5.1. """ packet = b''.join( [NS(b'foo'), NS(b'none'), NS(b'password'), chr(0), NS(b'foo')]) d = self.authServer.ssh_USERAUTH_REQUEST(packet) def check(ignored): self.assertEqual(self.authServer.transport.packets, [(userauth.MSG_USERAUTH_SUCCESS, b'')]) return d.addCallback(check)
def realPath(self, path): """ Convert any path to an absolute path. This method returns the absolute path as a string, or a Deferred that returns the same. @type path: L{bytes} @param path: the path to convert as a string. """ d = self._sendRequest(FXP_REALPATH, NS(path)) return d.addCallback(self._cbRealPath)
def test_USERAUTH_FAILURE_sorting(self): """ ssh_USERAUTH_FAILURE should sort the methods by their position in SSHUserAuthClient.preferredOrder. Methods that are not in preferredOrder should be sorted at the end of that list. """ def auth_firstmethod(): self.authClient.transport.sendPacket(255, 'here is data') def auth_anothermethod(): self.authClient.transport.sendPacket(254, 'other data') return True self.authClient.auth_firstmethod = auth_firstmethod self.authClient.auth_anothermethod = auth_anothermethod self.authClient.ssh_USERAUTH_FAILURE( NS('afirstmethod,password') + '\x00') # should send password packet self.assertEquals(self.authClient.transport.packets[-1], (userauth.MSG_USERAUTH_REQUEST, NS('foo') + NS('nancy') + NS('password') + '\x00' + NS('foo'))) self.authClient.ssh_USERAUTH_FAILURE( NS('firstmethod,anothermethod,password') + '\xff') self.assertEquals(self.authClient.transport.packets[-2:], [(255, 'here is data'), (254, 'other data')])
def auth_publickey(self, packet): """ Public key authentication. Payload:: byte has signature string algorithm name string key blob [string signature] (if has signature is True) Create a SSHPublicKey credential and verify it using our portal. """ hasSig = ord(packet[0]) algName, blob, rest = getNS(packet[1:], 2) pubKey = keys.Key.fromString(blob) signature = hasSig and getNS(rest)[0] or None if hasSig: b = (NS(self.transport.sessionID) + chr(MSG_USERAUTH_REQUEST) + NS(self.user) + NS(self.nextService) + NS('publickey') + chr(hasSig) + NS(pubKey.sshType()) + NS(blob)) c = credentials.SSHPrivateKey(self.user, algName, blob, b, signature) return self.portal.login(c, None, interfaces.IConchUser) else: c = credentials.SSHPrivateKey(self.user, algName, blob, None, None) return self.portal.login(c, None, interfaces.IConchUser).addErrback(self._ebCheckKey, packet[1:])
def test_USERAUTH_FAILURE_sorting(self): """ ssh_USERAUTH_FAILURE should sort the methods by their position in SSHUserAuthClient.preferredOrder. Methods that are not in preferredOrder should be sorted at the end of that list. """ def auth_firstmethod(): self.authClient.transport.sendPacket(255, b'here is data') def auth_anothermethod(): self.authClient.transport.sendPacket(254, b'other data') return True self.authClient.auth_firstmethod = auth_firstmethod self.authClient.auth_anothermethod = auth_anothermethod # although they shouldn't get called, method callbacks auth_* MUST # exist in order for the test to work properly. self.authClient.ssh_USERAUTH_FAILURE( NS(b'anothermethod,password') + b'\x00') # should send password packet self.assertEqual( self.authClient.transport.packets[-1], (userauth.MSG_USERAUTH_REQUEST, NS(b'foo') + NS(b'nancy') + NS(b'password') + b'\x00' + NS(b'foo'))) self.authClient.ssh_USERAUTH_FAILURE( NS(b'firstmethod,anothermethod,password') + b'\xff') self.assertEqual(self.authClient.transport.packets[-2:], [(255, b'here is data'), (254, b'other data')])
def auth_publickey(self, packet): """ Public key authentication. Payload:: byte has signature string algorithm name string key blob [string signature] (if has signature is True) Create a SSHPublicKey credential and verify it using our portal. """ hasSig = ord(packet[0:1]) algName, blob, rest = getNS(packet[1:], 2) try: pubKey = keys.Key.fromString(blob) except keys.BadKeyError: error = "Unsupported key type {} or bad key".format( algName.decode("ascii")) self._log.error(error) return defer.fail(UnauthorizedLogin(error)) signature = hasSig and getNS(rest)[0] or None if hasSig: b = (NS(self.transport.sessionID) + bytes( (MSG_USERAUTH_REQUEST, )) + NS(self.user) + NS(self.nextService) + NS(b"publickey") + bytes( (hasSig, )) + NS(pubKey.sshType()) + NS(blob)) c = credentials.SSHPrivateKey(self.user, algName, blob, b, signature) return self.portal.login(c, None, interfaces.IConchUser) else: c = credentials.SSHPrivateKey(self.user, algName, blob, None, None) return self.portal.login(c, None, interfaces.IConchUser).addErrback( self._ebCheckKey, packet[1:])
def setAttrs(self, path, attrs): """ Set the attributes for the path. This method returns when the attributes are set or a Deferred that is called back when they are. @param path: the path to set attributes for as a string. @param attrs: a dictionary in the same format as the attrs argument to openFile. """ data = NS(path) + self._packAttributes(attrs) return self._sendRequest(FXP_SETSTAT, data)
def channelOpen(self, _): def ptyReqFailed(reason): # TODO(vladum): Why is this never called? Looks like the Transport # received the error (at least the packet integrity ones). err("SSH PTY Request failed") self.reason = reason self.conn.sendClose(self) modes = pack("<B", 0x00) # only TTY_OP_END win_size = (0, 0, 0, 0) # 0s are ignored pty_req_data = packRequest_pty_req('vt100', win_size, modes) d = self.conn.sendRequest(self, 'pty-req', pty_req_data, wantReply=True) #Set all the env variables we've got for key, value in self.env: d.addCallback( lambda _: self.conn.sendRequest(self, 'env', NS(key), NS(value), wantReply=True) ) d.addCallback( # send command after we get the pty lambda _: self.conn.sendRequest(self, 'exec', NS(self.command)) ) d.addErrback(ptyReqFailed)
def agentc_SIGN_REQUEST(self, data): """ Data is a structure with a reference to an already added key object and some data that the clients wants signed with that key. If the key object wasn't loaded, return AGENT_FAILURE, else return the signature. """ blob, data = getNS(data) if blob not in self.factory.keys: return self.sendResponse(AGENT_FAILURE, '') signData, data = getNS(data) assert data == '\000\000\000\000' self.sendResponse(AGENT_SIGN_RESPONSE, NS(self.factory.keys[blob][0].sign(signData)))
def _cbGenericAnswers(self, responses): """ Called back when we are finished answering keyboard-interactive questions. Send the info back to the server in a MSG_USERAUTH_INFO_RESPONSE. @param responses: a list of C{str} responses @type responses: C{list} """ data = struct.pack('!L', len(responses)) for r in responses: data += NS(r.encode('UTF8')) self.transport.sendPacket(MSG_USERAUTH_INFO_RESPONSE, data)
def ssh_KEX_DH_GEX_REQUEST_OLD(self, packet): """ This represents two different key exchange methods that share the same integer value. KEXDH_INIT (for diffie-hellman-group1-sha1 exchanges) payload:: integer e (the client's Diffie-Hellman public key) We send the KEXDH_REPLY with our host key and signature. KEX_DH_GEX_REQUEST_OLD (for diffie-hellman-group-exchange-sha1) payload:: integer ideal (ideal size for the Diffie-Hellman prime) We send the KEX_DH_GEX_GROUP message with the group that is closest in size to ideal. If we were told to ignore the next key exchange packet by ssh_KEXINIT, drop it on the floor and return. """ if self.ignoreNextPacket: self.ignoreNextPacket = 0 return if self.kexAlg == 'diffie-hellman-group1-sha1': # this is really KEXDH_INIT clientDHpublicKey, foo = getMP(packet) y = Util.number.getRandomNumber(512, randbytes.secureRandom) serverDHpublicKey = _MPpow(DH_GENERATOR, y, DH_PRIME) sharedSecret = _MPpow(clientDHpublicKey, y, DH_PRIME) h = sha1() h.update(NS(self.otherVersionString)) h.update(NS(self.ourVersionString)) h.update(NS(self.otherKexInitPayload)) h.update(NS(self.ourKexInitPayload)) h.update(NS(self.factory.publicKeys[self.keyAlg].blob())) h.update(MP(clientDHpublicKey)) h.update(serverDHpublicKey) h.update(sharedSecret) exchangeHash = h.digest() self.sendPacket( MSG_KEXDH_REPLY, NS(self.factory.publicKeys[self.keyAlg].blob()) + serverDHpublicKey + NS(self.factory.privateKeys[self.keyAlg].sign(exchangeHash))) self._keySetup(sharedSecret, exchangeHash) elif self.kexAlg == 'diffie-hellman-group-exchange-sha1': self.dhGexRequest = packet ideal = struct.unpack('>L', packet)[0] self.g, self.p = self.factory.getDHPrime(ideal) self.sendPacket(MSG_KEX_DH_GEX_GROUP, MP(self.p) + MP(self.g)) else: raise error.ConchError('bad kexalg: %s' % self.kexAlg)
def extendedRequest(self, request, data): """ Make an extended request of the server. The method returns a Deferred that is called back with the result of the extended request. @type request: L{bytes} @param request: the name of the extended request to make. @type data: L{bytes} @param data: any other data that goes along with the request. """ return self._sendRequest(FXP_EXTENDED, NS(request) + data)
def channelOpen(self, ignored): """ Create a pty by sending a pty-req to the server """ term = 'xterm' winSize = (25, 80, 0, 0) ptyReqData = session.packRequest_pty_req(term, winSize, '') self.conn.sendRequest(self, 'pty-req', ptyReqData) command = self.conn.sendRequest(self, 'exec', NS(self._command), wantReply=True) command.addCallbacks(self._execSuccess, self._execFailure)
def test_invalid_USERAUTH_INFO_RESPONSE_not_enough_data(self): """ If ssh_USERAUTH_INFO_RESPONSE gets an invalid packet, the user authentication should fail. """ packet = (NS('foo') + NS('none') + NS('keyboard-interactive') + NS('') + NS('')) d = self.authServer.ssh_USERAUTH_REQUEST(packet) self.authServer.ssh_USERAUTH_INFO_RESPONSE( NS('\x00\x00\x00\x00' + NS('hi'))) return d.addCallback(self._checkFailed)
def makeDirectory(self, path, attrs): """ Make a directory. This method returns a Deferred that is called back when it is created. @type path: L{bytes} @param path: the name of the directory to create as a string. @param attrs: a dictionary of attributes to create the directory with. Its meaning is the same as the attrs in the openFile method. """ return self._sendRequest(FXP_MKDIR, NS(path) + self._packAttributes(attrs))
def test_tooManyAttempts(self): """ Test that the server disconnects if the client fails authentication too many times. """ packet = b''.join( [NS(b'foo'), NS(b'none'), NS(b'password'), b'\0', NS(b'bar')]) self.authServer.clock = task.Clock() for i in range(21): d = self.authServer.ssh_USERAUTH_REQUEST(packet) self.authServer.clock.advance(2) def check(ignored): self.assertEqual( self.authServer.transport.packets[-1], (transport.MSG_DISCONNECT, b'\x00' * 3 + bytes( (transport.DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, )) + NS(b"too many bad auths") + NS(b''))) return d.addCallback(check)
def channelOpen(self, ignoredData): req = packRequest_pty_req(self.conn.client.term, self.conn.client.size, "") self.conn.sendRequest(self, "pty-req", req) d = self.conn.sendRequest(self, 'exec', NS(" ".join(command)), wantReply=1) @d.addCallback def cb(chaff): other = self.conn.client.proxy self.proxy.setPeer(other) other.setPeer(self.proxy)
def getAttrs(self, path, followLinks=0): """ Return the attributes for the given path. This method returns a dictionary in the same format as the attrs argument to openFile or a Deferred that is called back with same. @param path: the path to return attributes for as a string. @param followLinks: a boolean. if it is True, follow symbolic links and return attributes for the real path at the base. if it is False, return attributes for the specified path. """ if followLinks: m = FXP_STAT else: m = FXP_LSTAT return self._sendRequest(m, NS(path))
def channelOpen(self, whatever): """ Called when the channel is opened. "whatever" is any data that the other side sent us when opening the channel. @type whatever: L{bytes} """ yield self.conn.sendRequest(self, "subsystem", NS("sftp"), wantReply=True) client = SFTPClient() client.makeConnection(self) self.dataReceived = client.dataReceived self.conn.notifyClientIsReady(client)
def test_keyboardInteractive(self): """ Make sure that the client can authenticate with the keyboard interactive method. """ self.authClient.ssh_USERAUTH_PK_OK_keyboard_interactive( NS(b'') + NS(b'') + NS(b'') + b'\x00\x00\x00\x01' + NS(b'Password: '******'\x00') self.assertEqual(self.authClient.transport.packets[-1], (userauth.MSG_USERAUTH_INFO_RESPONSE, b'\x00\x00\x00\x02' + NS(b'foo') + NS(b'foo')))
def test_failedPAMAuthentication(self): """ Test that keyboard-interactive authentication fails. """ packet = (NS('foo') + NS('none') + NS('keyboard-interactive') + NS('') + NS('')) response = '\x00\x00\x00\x02' + NS('bar') + NS('bar') d = self.authServer.ssh_USERAUTH_REQUEST(packet) self.authServer.ssh_USERAUTH_INFO_RESPONSE(response) def check(ignored): self.assertEqual(self.authServer.transport.packets[0], (userauth.MSG_USERAUTH_INFO_REQUEST, (NS('') + NS('') + NS('') + '\x00\x00\x00\x02' + NS('Name: ') + '\x01' + NS('Password: '******'\x00'))) return d.addCallback(check).addCallback(self._checkFailed)
def test_failedPrivateKeyAuthenticationWithSignature(self): """ Test that private key authentication fails when the public key is invalid. """ blob = keys.Key.fromString(keydata.publicRSA_openssh).blob() obj = keys.Key.fromString(keydata.privateRSA_openssh) packet = (NS(b'foo') + NS(b'none') + NS(b'publickey') + b'\xff' + NS(b'ssh-rsa') + NS(blob) + NS(obj.sign(blob))) self.authServer.transport.sessionID = b'test' d = self.authServer.ssh_USERAUTH_REQUEST(packet) return d.addCallback(self._checkFailed)
def ssh_SERVICE_REQUEST(self, packet): """ Called when we get a MSG_SERVICE_REQUEST message. Payload:: string serviceName The client has requested a service. If we can start the service, start it; otherwise, disconnect with DISCONNECT_SERVICE_NOT_AVAILABLE. """ service, rest = getNS(packet) cls = self.factory.getService(self, service) if not cls: self.sendDisconnect(DISCONNECT_SERVICE_NOT_AVAILABLE, "don't have service %s" % service) return else: self.sendPacket(MSG_SERVICE_ACCEPT, NS(service)) self.setService(cls())
def test_successfulPrivateKeyAuthentication(self): """ Test that private key authentication completes successfully, """ blob = keys.Key.fromString(keydata.publicRSA_openssh).blob() obj = keys.Key.fromString(keydata.privateRSA_openssh) packet = (NS('foo') + NS('none') + NS('publickey') + '\xff' + NS(obj.sshType()) + NS(blob)) self.authServer.transport.sessionID = 'test' signature = obj.sign(NS('test') + chr(userauth.MSG_USERAUTH_REQUEST) + packet) packet += NS(signature) d = self.authServer.ssh_USERAUTH_REQUEST(packet) def check(ignored): self.assertEqual(self.authServer.transport.packets, [(userauth.MSG_USERAUTH_SUCCESS, '')]) return d.addCallback(check)