| 1 | # Copyright (c) Twisted Matrix Laboratories. |
|---|
| 2 | # See LICENSE for details. |
|---|
| 3 | |
|---|
| 4 | """ |
|---|
| 5 | A Factory for SSH servers, along with an OpenSSHFactory to use the same |
|---|
| 6 | data sources as OpenSSH. |
|---|
| 7 | |
|---|
| 8 | Maintainer: Paul Swartz |
|---|
| 9 | """ |
|---|
| 10 | |
|---|
| 11 | from twisted.internet import protocol |
|---|
| 12 | from twisted.python import log |
|---|
| 13 | from twisted.python.reflect import qual |
|---|
| 14 | |
|---|
| 15 | from twisted.conch import error |
|---|
| 16 | from twisted.conch.ssh import keys |
|---|
| 17 | import transport, userauth, connection |
|---|
| 18 | |
|---|
| 19 | import random |
|---|
| 20 | import warnings |
|---|
| 21 | |
|---|
| 22 | class SSHFactory(protocol.Factory): |
|---|
| 23 | """ |
|---|
| 24 | A Factory for SSH servers. |
|---|
| 25 | """ |
|---|
| 26 | protocol = transport.SSHServerTransport |
|---|
| 27 | |
|---|
| 28 | services = { |
|---|
| 29 | 'ssh-userauth':userauth.SSHUserAuthServer, |
|---|
| 30 | 'ssh-connection':connection.SSHConnection |
|---|
| 31 | } |
|---|
| 32 | def startFactory(self): |
|---|
| 33 | """ |
|---|
| 34 | Check for public and private keys. |
|---|
| 35 | """ |
|---|
| 36 | if not hasattr(self,'publicKeys'): |
|---|
| 37 | self.publicKeys = self.getPublicKeys() |
|---|
| 38 | for keyType, value in self.publicKeys.items(): |
|---|
| 39 | if isinstance(value, str): |
|---|
| 40 | warnings.warn("Returning a mapping from strings to " |
|---|
| 41 | "strings from getPublicKeys()/publicKeys (in %s) " |
|---|
| 42 | "is deprecated. Return a mapping from " |
|---|
| 43 | "strings to Key objects instead." % |
|---|
| 44 | (qual(self.__class__)), |
|---|
| 45 | DeprecationWarning, stacklevel=1) |
|---|
| 46 | self.publicKeys[keyType] = keys.Key.fromString(value) |
|---|
| 47 | if not hasattr(self,'privateKeys'): |
|---|
| 48 | self.privateKeys = self.getPrivateKeys() |
|---|
| 49 | for keyType, value in self.privateKeys.items(): |
|---|
| 50 | if not isinstance(value, keys.Key): |
|---|
| 51 | warnings.warn("Returning a mapping from strings to " |
|---|
| 52 | "PyCrypto key objects from " |
|---|
| 53 | "getPrivateKeys()/privateKeys (in %s) " |
|---|
| 54 | "is deprecated. Return a mapping from " |
|---|
| 55 | "strings to Key objects instead." % |
|---|
| 56 | (qual(self.__class__),), |
|---|
| 57 | DeprecationWarning, stacklevel=1) |
|---|
| 58 | self.privateKeys[keyType] = keys.Key(value) |
|---|
| 59 | if not self.publicKeys or not self.privateKeys: |
|---|
| 60 | raise error.ConchError('no host keys, failing') |
|---|
| 61 | if not hasattr(self,'primes'): |
|---|
| 62 | self.primes = self.getPrimes() |
|---|
| 63 | |
|---|
| 64 | |
|---|
| 65 | def buildProtocol(self, addr): |
|---|
| 66 | """ |
|---|
| 67 | Create an instance of the server side of the SSH protocol. |
|---|
| 68 | |
|---|
| 69 | @type addr: L{twisted.internet.interfaces.IAddress} provider |
|---|
| 70 | @param addr: The address at which the server will listen. |
|---|
| 71 | |
|---|
| 72 | @rtype: L{twisted.conch.ssh.SSHServerTransport} |
|---|
| 73 | @return: The built transport. |
|---|
| 74 | """ |
|---|
| 75 | t = protocol.Factory.buildProtocol(self, addr) |
|---|
| 76 | t.supportedPublicKeys = self.privateKeys.keys() |
|---|
| 77 | if not self.primes: |
|---|
| 78 | log.msg('disabling diffie-hellman-group-exchange because we ' |
|---|
| 79 | 'cannot find moduli file') |
|---|
| 80 | ske = t.supportedKeyExchanges[:] |
|---|
| 81 | ske.remove('diffie-hellman-group-exchange-sha1') |
|---|
| 82 | t.supportedKeyExchanges = ske |
|---|
| 83 | return t |
|---|
| 84 | |
|---|
| 85 | |
|---|
| 86 | def getPublicKeys(self): |
|---|
| 87 | """ |
|---|
| 88 | Called when the factory is started to get the public portions of the |
|---|
| 89 | servers host keys. Returns a dictionary mapping SSH key types to |
|---|
| 90 | public key strings. |
|---|
| 91 | |
|---|
| 92 | @rtype: C{dict} |
|---|
| 93 | """ |
|---|
| 94 | raise NotImplementedError('getPublicKeys unimplemented') |
|---|
| 95 | |
|---|
| 96 | |
|---|
| 97 | def getPrivateKeys(self): |
|---|
| 98 | """ |
|---|
| 99 | Called when the factory is started to get the private portions of the |
|---|
| 100 | servers host keys. Returns a dictionary mapping SSH key types to |
|---|
| 101 | C{Crypto.PublicKey.pubkey.pubkey} objects. |
|---|
| 102 | |
|---|
| 103 | @rtype: C{dict} |
|---|
| 104 | """ |
|---|
| 105 | raise NotImplementedError('getPrivateKeys unimplemented') |
|---|
| 106 | |
|---|
| 107 | |
|---|
| 108 | def getPrimes(self): |
|---|
| 109 | """ |
|---|
| 110 | Called when the factory is started to get Diffie-Hellman generators and |
|---|
| 111 | primes to use. Returns a dictionary mapping number of bits to lists |
|---|
| 112 | of tuple of (generator, prime). |
|---|
| 113 | |
|---|
| 114 | @rtype: C{dict} |
|---|
| 115 | """ |
|---|
| 116 | |
|---|
| 117 | |
|---|
| 118 | def getDHPrime(self, bits): |
|---|
| 119 | """ |
|---|
| 120 | Return a tuple of (g, p) for a Diffe-Hellman process, with p being as |
|---|
| 121 | close to bits bits as possible. |
|---|
| 122 | |
|---|
| 123 | @type bits: C{int} |
|---|
| 124 | @rtype: C{tuple} |
|---|
| 125 | """ |
|---|
| 126 | primesKeys = self.primes.keys() |
|---|
| 127 | primesKeys.sort(lambda x, y: cmp(abs(x - bits), abs(y - bits))) |
|---|
| 128 | realBits = primesKeys[0] |
|---|
| 129 | return random.choice(self.primes[realBits]) |
|---|
| 130 | |
|---|
| 131 | |
|---|
| 132 | def getService(self, transport, service): |
|---|
| 133 | """ |
|---|
| 134 | Return a class to use as a service for the given transport. |
|---|
| 135 | |
|---|
| 136 | @type transport: L{transport.SSHServerTransport} |
|---|
| 137 | @type service: C{str} |
|---|
| 138 | @rtype: subclass of L{service.SSHService} |
|---|
| 139 | """ |
|---|
| 140 | if service == 'ssh-userauth' or hasattr(transport, 'avatar'): |
|---|
| 141 | return self.services[service] |
|---|