def getKeyPair(pubkeyfile, privkeyfile): """ This function looks for RSA keypair files in the current directory. If they do not exist, the keypair is created. """ if not (os.path.exists(pubkeyfile) and os.path.exists(privkeyfile)): # No keypair exists. Generate a new RSA keypair from Crypto.PublicKey import RSA rsa_key = Key(RSA.generate(_KEY_LENGTH)) public_key_string = rsa_key.public().toString(type="OPENSSH") private_key_string = rsa_key.toString(type="OPENSSH") # save keys for the future. with open(privkeyfile, 'wt') as pfile: pfile.write(private_key_string) print("Created SSH private key in '{}'".format(_PRIVATE_KEY_FILE)) with open(pubkeyfile, 'wt') as pfile: pfile.write(public_key_string) print("Created SSH public key in '{}'".format(_PUBLIC_KEY_FILE)) else: with open(pubkeyfile) as pfile: public_key_string = pfile.read() with open(privkeyfile) as pfile: private_key_string = pfile.read() return Key.fromString(public_key_string), Key.fromString(private_key_string)
def test_saveKeysha256(self): """ L{_saveKey} will generate key fingerprint in L{FingerprintFormats.SHA256-BASE64} format if explicitly specified. """ base = FilePath(self.mktemp()) base.makedirs() filename = base.child('id_rsa').path key = Key.fromString(privateRSA_openssh) _saveKey(key, {'filename': filename, 'pass': '******', 'format': 'sha256-base64'}) self.assertEqual( self.stdout.getvalue(), "Your identification has been saved in %s\n" "Your public key has been saved in %s.pub\n" "The key fingerprint in <FingerprintFormats=SHA256_BASE64> is:\n" "ryaugIFT0B8ItuszldMEU7q14rG/wj9HkRosMeBWkts=\n" % ( filename, filename)) self.assertEqual( key.fromString( base.child('id_rsa').getContent(), None, 'passphrase'), key) self.assertEqual( Key.fromString(base.child('id_rsa.pub').getContent()), key.public())
def getKeyPair(pubkeyfile, privkeyfile): """ This function looks for RSA keypair files in the current directory. If they do not exist, the keypair is created. """ if not (os.path.exists(pubkeyfile) and os.path.exists(privkeyfile)): # No keypair exists. Generate a new RSA keypair print(" Generating SSH RSA keypair ...", end=' ') from Crypto.PublicKey import RSA KEY_LENGTH = 1024 rsaKey = Key(RSA.generate(KEY_LENGTH)) publicKeyString = rsaKey.public().toString(type="OPENSSH") privateKeyString = rsaKey.toString(type="OPENSSH") # save keys for the future. file(pubkeyfile, 'w+b').write(publicKeyString) file(privkeyfile, 'w+b').write(privateKeyString) print(" done.") else: publicKeyString = file(pubkeyfile).read() privateKeyString = file(privkeyfile).read() return Key.fromString(publicKeyString), Key.fromString(privateKeyString)
def _check_public_key (self, key, ) : try : Key.fromString(data=key, ) except BadKeyError : raise _exceptions.BAD_ARGUMENT("invalid public key.") return key.strip()
def test_savingsPreservesExisting(self): """ L{KnownHostsFile.save} will not overwrite existing entries in its save path, even if they were only added after the L{KnownHostsFile} instance was initialized. """ # Start off with one host/key pair in the file path = self.pathWithContent(sampleHashedLine) knownHosts = KnownHostsFile.fromPath(path) # After initializing the KnownHostsFile instance, add a second host/key # pair to the file directly - without the instance's help or knowledge. with path.open("a") as hostsFileObj: hostsFileObj.write(otherSamplePlaintextLine) # Add a third host/key pair using the KnownHostsFile instance key = Key.fromString(thirdSampleKey) knownHosts.addHostKey("brandnew.example.com", key) knownHosts.save() # Check that all three host/key pairs are present. knownHosts = KnownHostsFile.fromPath(path) self.assertEqual([True, True, True], [ knownHosts.hasHostKey( "www.twistedmatrix.com", Key.fromString(sampleKey)), knownHosts.hasHostKey( "divmod.com", Key.fromString(otherSampleKey)), knownHosts.hasHostKey("brandnew.example.com", key)])
def test_saveKey(self): """ L{_saveKey} writes the private and public parts of a key to two different files and writes a report of this to standard out. """ base = FilePath(self.mktemp()) base.makedirs() filename = base.child('id_rsa').path key = Key.fromString(privateRSA_openssh) _saveKey( key.keyObject, {'filename': filename, 'pass': '******'}) self.assertEqual( self.stdout.getvalue(), "Your identification has been saved in %s\n" "Your public key has been saved in %s.pub\n" "The key fingerprint is:\n" "3d:13:5f:cb:c9:79:8a:93:06:27:65:bc:3d:0b:8f:af\n" % ( filename, filename)) self.assertEqual( key.fromString( base.child('id_rsa').getContent(), None, 'passphrase'), key) self.assertEqual( Key.fromString(base.child('id_rsa.pub').getContent()), key.public())
def test_saveKeyECDSA(self): """ L{_saveKey} writes the private and public parts of a key to two different files and writes a report of this to standard out. Test with ECDSA key. """ base = FilePath(self.mktemp()) base.makedirs() filename = base.child('id_ecdsa').path key = Key.fromString(privateECDSA_openssh) _saveKey(key, {'filename': filename, 'pass': '******', 'format': 'md5-hex'}) self.assertEqual( self.stdout.getvalue(), "Your identification has been saved in %s\n" "Your public key has been saved in %s.pub\n" "The key fingerprint in <FingerprintFormats=MD5_HEX> is:\n" "e2:3b:e8:1c:f8:c9:c7:de:8b:c0:00:68:2e:c9:2c:8a\n" % ( filename, filename)) self.assertEqual( key.fromString( base.child('id_ecdsa').getContent(), None, 'passphrase'), key) self.assertEqual( Key.fromString(base.child('id_ecdsa.pub').getContent()), key.public())
def __init__(self, options): self.options = options # load private key with open(options['host-key']) as privateBlobFile: privateBlob = privateBlobFile.read() privateKey = Key.fromString(data=privateBlob) # load public key with open(options['host-key']+'.pub') as publicBlobFile: publicBlob = publicBlobFile.read() publicKey = Key.fromString(data=publicBlob) tickets = {} factory = AMPSSHFactory(tickets, self.options) factory.services['ssh-connection'] = SSHConnection # Load in keys the way SSHFactory expects them. factory.privateKeys = {'ssh-rsa': privateKey} factory.publicKeys = {'ssh-rsa': publicKey} # Give it a portal to authenticate clients with factory.portal = Portal(SimpleRealm(tickets, options, factory)) # validate against keys in authorized_keys files checker = AuthorizedKeys(options['authorized-keys']) factory.portal.registerChecker(checker) # init TCPServer with port and factory. internet.TCPServer.__init__(self, options['ssh-port'], factory) # remember for future reference self.factory = factory
def manhole(username, password, globals): """Starts a ssh listener with password authentication using the given username and password. Clients connecting to the ssh listener will find themselves in a colored python shell with the supplied globals. Args: username(str): The username ssh clients should auth with. password(str): The password ssh clients should auth with. globals(dict): The variables to expose in the shell. Returns: twisted.internet.protocol.Factory: A factory to pass to ``listenTCP`` """ if not isinstance(password, bytes): password = password.encode('ascii') checker = checkers.InMemoryUsernamePasswordDatabaseDontUse( **{username: password} ) rlm = manhole_ssh.TerminalRealm() rlm.chainedProtocolFactory = lambda: insults.ServerProtocol( SynapseManhole, dict(globals, __name__="__console__") ) factory = manhole_ssh.ConchFactory(portal.Portal(rlm, [checker])) factory.publicKeys[b'ssh-rsa'] = Key.fromString(PUBLIC_KEY) factory.privateKeys[b'ssh-rsa'] = Key.fromString(PRIVATE_KEY) return factory
def main(keys_path, username_get=None, gid=None, port=2022): settings.username_get = username_get settings.gid = gid key_path = keys_path + '/id_rsa' if not os.path.exists(key_path): subprocess.check_call(['ssh-keygen', '-f', key_path, '-t', 'rsa', '-N', '']) with open(key_path) as privateBlobFile: privateBlob = privateBlobFile.read() privateKey = Key.fromString(data=privateBlob) with open(key_path + '.pub') as publicBlobFile: publicBlob = publicBlobFile.read() publicKey = Key.fromString(data=publicBlob) factory = SSHFactory() factory.privateKeys = {'ssh-rsa': privateKey} factory.publicKeys = {'ssh-rsa': publicKey} factory.portal = Portal(KeyRealm()) factory.portal.registerChecker(KeyChecker()) reactor.listenTCP(port, factory) reactor.run()
def getRSAKeys(): print "running getRSAKeys" with open('/home/pi/.ssh/id_rsa') as privateBlobFile: privateBlob = privateBlobFile.read() privateKey = Key.fromString(data=privateBlob) with open('/home/pi/.ssh/id_rsa.pub') as publicBlobFile: publicBlob = publicBlobFile.read() publicKey = Key.fromString(data=publicBlob) return publicKey, privateKey
def test_verifyInvalidKey(self): """ Verifying an invalid key should return a L{Deferred} which fires with a L{HostKeyChanged} failure. """ hostsFile = self.loadSampleHostsFile() wrongKey = Key.fromString(thirdSampleKey) ui = FakeUI() hostsFile.addHostKey("1.2.3.4", Key.fromString(sampleKey)) d = hostsFile.verifyHostKey( ui, "www.twistedmatrix.com", "1.2.3.4", wrongKey) return self.assertFailure(d, HostKeyChanged)
def test_notPresentKey(self): """ L{KnownHostsFile.hasHostKey} returns C{False} when a key for the given hostname is not present. """ hostsFile = self.loadSampleHostsFile() self.assertFalse(hostsFile.hasHostKey( b"non-existent.example.com", Key.fromString(sampleKey))) self.assertTrue(hostsFile.hasHostKey( b"www.twistedmatrix.com", Key.fromString(sampleKey))) self.assertFalse(hostsFile.hasHostKey( b"www.twistedmatrix.com", Key.fromString(ecdsaSampleKey)))
def test_matchesKey(self): """ L{IKnownHostEntry.matchesKey} checks to see if an entry matches a given SSH key. """ twistedmatrixDotCom = Key.fromString(sampleKey) divmodDotCom = Key.fromString(otherSampleKey) self.assertEqual( True, self.entry.matchesKey(twistedmatrixDotCom)) self.assertEqual( False, self.entry.matchesKey(divmodDotCom))
def test_verifyValidKey(self): """ Verifying a valid key should return a L{Deferred} which fires with True. """ hostsFile = self.loadSampleHostsFile() hostsFile.addHostKey("1.2.3.4", Key.fromString(sampleKey)) ui = FakeUI() d = hostsFile.verifyHostKey(ui, "www.twistedmatrix.com", "1.2.3.4", Key.fromString(sampleKey)) l = [] d.addCallback(l.append) self.assertEqual(l, [True])
def test_equality(self): """ Two L{HashedEntry} instances compare equal if and only if they represent the same host and key in exactly the same way: the host salt, host hash, public key type, public key, and comment fields must all be equal. """ hostSalt = b"gJbSEPBG9ZSBoZpHNtZBD1bHKBA" hostHash = b"bQv+0Xa0dByrwkA1EB0E7Xop/Fo" publicKey = Key.fromString(sampleKey) keyType = networkString(publicKey.type()) comment = b"hello, world" entry = HashedEntry( hostSalt, hostHash, keyType, publicKey, comment) duplicate = HashedEntry( hostSalt, hostHash, keyType, publicKey, comment) # Vary the host salt self.assertNormalEqualityImplementation( entry, duplicate, HashedEntry( hostSalt[::-1], hostHash, keyType, publicKey, comment)) # Vary the host hash self.assertNormalEqualityImplementation( entry, duplicate, HashedEntry( hostSalt, hostHash[::-1], keyType, publicKey, comment)) # Vary the key type self.assertNormalEqualityImplementation( entry, duplicate, HashedEntry( hostSalt, hostHash, keyType[::-1], publicKey, comment)) # Vary the key self.assertNormalEqualityImplementation( entry, duplicate, HashedEntry( hostSalt, hostHash, keyType, Key.fromString(otherSampleKey), comment)) # Vary the comment self.assertNormalEqualityImplementation( entry, duplicate, HashedEntry( hostSalt, hostHash, keyType, publicKey, comment[::-1]))
def test_verifyInvalidKey(self): """ Verfying an invalid key should return a L{Deferred} which fires with a L{HostKeyChanged} failure. """ hostsFile = self.loadSampleHostsFile() wrongKey = Key.fromString(thirdSampleKey) ui = FakeUI() hostsFile.addHostKey("1.2.3.4", Key.fromString(sampleKey)) d = hostsFile.verifyHostKey( ui, "www.twistedmatrix.com", "1.2.3.4", wrongKey) l = [] d.addErrback(l.append) hkc = l[0].trap(HostKeyChanged)
def test_saveResetsClobberState(self): """ After L{KnownHostsFile.save} is used once with an instance initialized by the default initializer, contents of the save path are respected and preserved. """ hostsFile = KnownHostsFile(self.pathWithContent(sampleHashedLine)) preSave = hostsFile.addHostKey( "www.example.com", Key.fromString(otherSampleKey)) hostsFile.save() postSave = hostsFile.addHostKey( "another.example.com", Key.fromString(thirdSampleKey)) hostsFile.save() self.assertEqual([preSave, postSave], list(hostsFile.iterentries()))
def test_verifyHostIPMismatch(self): """ Verifying a key where the host is present (and correct), but the IP is present and different, should result the deferred firing in a HostKeyChanged failure. """ hostsFile = self.loadSampleHostsFile() rightKey = Key.fromString(sampleKey) wrongKey = Key.fromString(thirdSampleKey) ui = FakeUI() l = [] d = hostsFile.verifyHostKey( ui, "www.twistedmatrix.com", "4.3.2.1", wrongKey) d.addErrback(l.append) l[0].trap(HostKeyChanged)
def gce_provisioner( zone, project, ssh_public_key, gce_credentials=None ): """ Create an :class:`IProvisioner` for provisioning nodes on GCE. :param unicode zone: The name of the zone in which to provision instances. :param unicode project: The name of the project in which to provision instances. :param unicode ssh_public_key: The public key that will be put on the VM for ssh access. :param dict gce_credentials: A dict that has the same content as the json blob generated by the GCE console when you add a key to a service account. The service account must have permissions to spin up VMs in the specified project. :return: An class:`IProvisioner` provider for GCE instances. """ key = Key.fromString(bytes(ssh_public_key)) credentials = gce_credentials_from_config(gce_credentials) compute = discovery.build('compute', 'v1', credentials=credentials) return GCEProvisioner( instance_builder=GCEInstanceBuilder( zone=unicode(zone), project=unicode(project), compute=compute, ), ssh_public_key=key, )
def test_publicKeyAuthenticationFailure(self): """ If the SSH server rejects the key pair presented during authentication, the L{Deferred} returned by L{SSHCommandClientEndpoint.connect} fires with a L{Failure} wrapping L{AuthenticationFailed}. """ badKey = Key.fromString(privateRSA_openssh) self.setupKeyChecker(self.portal, {self.user: privateDSA_openssh}) endpoint = SSHCommandClientEndpoint.newConnection( self.reactor, b"/bin/ls -l", self.user, self.hostname, self.port, keys=[badKey], knownHosts=self.knownHosts, ui=FixedResponseUI(False)) factory = Factory() factory.protocol = Protocol connected = endpoint.connect(factory) server, client, pump = self.connectedServerAndClient( self.factory, self.reactor.tcpClients[0][2]) f = self.failureResultOf(connected) f.trap(AuthenticationFailed) # XXX Should assert something specific about the arguments of the # exception # Nothing useful can be done with the connection at this point, so the # endpoint should close it. self.assertTrue(client.transport.disconnecting)
def requestAvatarId(self, credentials): key = Key.fromString(credentials.blob) fingerprint = key.fingerprint().replace(':', '') self.meta.fingerprint = fingerprint if (credentials.username == 'git'): # Todo, maybe verify the key with a process protocol call # (drush or http) def success(): return credentials.username d = defer.maybeDeferred(success) d.addCallback(self.verify, credentials, key) return d else: """ If a user specified a non-git username, check that the user's key matches their username so that we can request a password if it does not.""" service = Service(AuthProtocol('drupalorg-ssh-user-key')) service.request_bool({"username":credentials.username}, {"fingerprint":fingerprint}) def auth_callback(result): if result: return credentials.username else: return Failure(UnauthorizedLogin(credentials.username)) service.addCallback(auth_callback) service.addCallback(self.verify, credentials, key) return service.deferred
def test_savedEntryAfterAddHasKeyMismatch(self): """ Even after a new entry has been added in memory but not yet saved, the L{HostKeyChanged} exception raised by L{KnownHostsFile.hasHostKey} has a C{lineno} attribute which indicates the 1-based line number of the offending entry in the underlying file when the given host key does not match the expected host key. """ hostsFile = self.loadSampleHostsFile() hostsFile.addHostKey( "www.example.com", Key.fromString(otherSampleKey)) exception = self.assertRaises( HostKeyChanged, hostsFile.hasHostKey, "www.twistedmatrix.com", Key.fromString(otherSampleKey)) self.assertEqual(exception.lineno, 1) self.assertEqual(exception.path, hostsFile.savePath)
def checkKey(self, credentials): fingerprint = Key.fromString(credentials.blob).fingerprint() fingerprint = fingerprint.replace(':','') for k in self.meta.pubkeys(credentials.username): if k == fingerprint: return True return False
def setUp(self): self.rsaPublic = Key.fromString(keydata.publicRSA_openssh) self.tmpdir = FilePath(self.mktemp()) self.tmpdir.makedirs() self.rsaFile = self.tmpdir.child('id_rsa') self.rsaFile.setContent(keydata.privateRSA_openssh) self.tmpdir.child('id_rsa.pub').setContent(keydata.publicRSA_openssh)
def test_getPrivateKeyPassphrase(self): """ L{SSHUserAuthClient} can get a private key from a file, and return a Deferred called back with a private L{Key} object, even if the key is encrypted. """ rsaPrivate = Key.fromString(keydata.privateRSA_openssh) passphrase = 'this is the passphrase' self.rsaFile.setContent(rsaPrivate.toString('openssh', passphrase)) options = ConchOptions() options.identitys = [self.rsaFile.path] client = SSHUserAuthClient("user", options, None) # Populate the list of used files client.getPublicKey() def _getPassword(prompt): self.assertEqual(prompt, "Enter passphrase for key '%s': " % ( self.rsaFile.path,)) return passphrase def _cbGetPrivateKey(key): self.assertEqual(key.isPublic(), False) self.assertEqual(key, rsaPrivate) self.patch(client, '_getPassword', _getPassword) return client.getPrivateKey().addCallback(_cbGetPrivateKey)
def test_mismatchedHostKey(self): """ If the SSH public key presented by the SSH server does not match the previously remembered key, as reported by the L{KnownHostsFile} instance use to construct the endpoint, for that server, the L{Deferred} returned by L{SSHCommandClientEndpoint.connect} fires with a L{Failure} wrapping L{HostKeyChanged}. """ differentKey = Key.fromString(privateDSA_openssh).public() knownHosts = KnownHostsFile(self.mktemp()) knownHosts.addHostKey(self.serverAddress.host, differentKey) knownHosts.addHostKey(self.hostname, differentKey) # The UI may answer true to any questions asked of it; they should # make no difference, since a *mismatched* key is not even optionally # allowed to complete a connection. ui = FixedResponseUI(True) endpoint = SSHCommandClientEndpoint.newConnection( self.reactor, b"/bin/ls -l", b"dummy user", self.hostname, self.port, password=b"dummy password", knownHosts=knownHosts, ui=ui) factory = Factory() factory.protocol = Protocol connected = endpoint.connect(factory) server, client, pump = self.connectedServerAndClient( self.factory, self.reactor.tcpClients[0][2]) f = self.failureResultOf(connected) f.trap(HostKeyChanged)
def _extractCommon(string): """ Extract common elements of base64 keys from an entry in a hosts file. @param string: A known hosts file entry (a single line). @type string: L{bytes} @return: a 4-tuple of hostname data (L{bytes}), ssh key type (L{bytes}), key (L{Key}), and comment (L{bytes} or L{None}). The hostname data is simply the beginning of the line up to the first occurrence of whitespace. @rtype: L{tuple} """ elements = string.split(None, 2) if len(elements) != 3: raise InvalidEntry() hostnames, keyType, keyAndComment = elements splitkey = keyAndComment.split(None, 1) if len(splitkey) == 2: keyString, comment = splitkey comment = comment.rstrip("\n") else: keyString = splitkey[0] comment = None key = Key.fromString(keyString.decode('base64')) return hostnames, keyType, key, comment
def requestAvatarId(self, credentials): key = Key.fromString(credentials.blob) fingerprint = key.fingerprint().replace(':', '') self.meta.fingerprint = fingerprint if (credentials.username == 'git'): # Todo, maybe verify the key with a process protocol call # (drush or http) def success(): return credentials.username d = defer.maybeDeferred(success) d.addCallback(self.verify, credentials, key) return d else: """ If a user specified a non-git username, check that the user's key matches their username so that we can request a password if it does not.""" drush_process = drush.DrushProcessProtocolBool('drupalorg-ssh-user-key') drush_process.call(credentials.username, fingerprint) def username(self): if self.result: return credentials.username else: return Failure(UnauthorizedLogin(credentials.username)) drush_process.deferred.addCallback(username) drush_process.deferred.addCallback(self.verify, credentials, key) return drush_process.deferred
def verifyNonPresentKey(self): """ Set up a test to verify a key that isn't present. Return a 3-tuple of the UI, a list set up to collect the result of the verifyHostKey call, and the sample L{KnownHostsFile} being used. This utility method avoids returning a L{Deferred}, and records results in the returned list instead, because the events which get generated here are pre-recorded in the 'ui' object. If the L{Deferred} in question does not fire, the it will fail quickly with an empty list. """ hostsFile = self.loadSampleHostsFile() absentKey = Key.fromString(thirdSampleKey) ui = FakeUI() l = [] d = hostsFile.verifyHostKey( ui, "sample-host.example.com", "4.3.2.1", absentKey) d.addBoth(l.append) self.assertEqual([], l) self.assertEqual( ui.promptText, "The authenticity of host 'sample-host.example.com (4.3.2.1)' " "can't be established.\n" "RSA key fingerprint is " "89:4e:cc:8c:57:83:96:48:ef:63:ad:ee:99:00:4c:8f.\n" "Are you sure you want to continue connecting (yes/no)? ") return ui, l, hostsFile
def verifyHostKey(self, hostKey, fingerprint): """ Ask the L{KnownHostsFile} provider available on the factory which created this protocol this protocol to verify the given host key. @return: A L{Deferred} which fires with the result of L{KnownHostsFile.verifyHostKey}. """ hostname = self.creator.hostname ip = self.transport.getPeer().host self._state = b'SECURING' d = self.creator.knownHosts.verifyHostKey(self.creator.ui, hostname, ip, Key.fromString(hostKey)) d.addErrback(self._saveHostKeyFailure) return d
def test_saveKeyEmptyPassphrase(self): """ L{_saveKey} will choose an empty string for the passphrase if no-passphrase is C{True}. """ base = FilePath(self.mktemp()) base.makedirs() filename = base.child('id_rsa').path key = Key.fromString(privateRSA_openssh) _saveKey( key.keyObject, {'filename': filename, 'no-passphrase': True}) self.assertEqual( key.fromString( base.child('id_rsa').getContent(), None, b''), key)
def test_saveKeyECDSAEmptyPassphrase(self): """ L{_saveKey} will choose an empty string for the passphrase if no-passphrase is C{True}. """ base = FilePath(self.mktemp()) base.makedirs() filename = base.child('id_ecdsa').path key = Key.fromString(privateECDSA_openssh) _saveKey(key, { 'filename': filename, 'no-passphrase': True, 'format': 'md5-hex' }) self.assertEqual( key.fromString(base.child('id_ecdsa').getContent(), None), key)
def test_saveKeyNoFilename(self): """ When no path is specified, it will ask for the path used to store the key. """ base = FilePath(self.mktemp()) base.makedirs() keyPath = base.child('custom_key').path self.patch(__builtin__, 'raw_input', lambda _: keyPath) key = Key.fromString(privateRSA_openssh) _saveKey(key, {'filename': None, 'no-passphrase': True}) persistedKeyContent = base.child('custom_key').getContent() persistedKey = key.fromString(persistedKeyContent, None, b'') self.assertEqual(key, persistedKey)
def test_displayPublicKeyEncryptedPassphrasePrompt(self): """ L{displayPublicKey} prints out the public key associated with a given private key, asking for the passphrase when it's encrypted. """ filename = self.mktemp() pubKey = Key.fromString(publicRSA_openssh) FilePath(filename).setContent(privateRSA_openssh_encrypted) self.patch(getpass, 'getpass', lambda x: 'encrypted') displayPublicKey({'filename': filename}) displayed = self.stdout.getvalue().strip('\n') if isinstance(displayed, unicode): displayed = displayed.encode("ascii") self.assertEqual( displayed, pubKey.toString('openssh'))
def test_saveKeyEd25519EmptyPassphrase(self): """ L{_saveKey} will choose an empty string for the passphrase if no-passphrase is C{True}. """ base = FilePath(self.mktemp()) base.makedirs() filename = base.child("id_ed25519").path key = Key.fromString(privateEd25519_openssh_new) _saveKey(key, { "filename": filename, "no-passphrase": True, "format": "md5-hex" }) self.assertEqual( key.fromString(base.child("id_ed25519").getContent(), None), key)
def test_getPublicKeyBadKeyError(self): """ If L{keys.Key.fromFile} raises a L{keys.BadKeyError}, the L{SSHUserAuthClient.getPublicKey} tries again to get a public key by calling itself recursively. """ options = ConchOptions() self.tmpdir.child('id_dsa.pub').setContent(keydata.publicDSA_openssh) dsaFile = self.tmpdir.child('id_dsa') dsaFile.setContent(keydata.privateDSA_openssh) options.identitys = [self.rsaFile.path, dsaFile.path] self.tmpdir.child('id_rsa.pub').setContent('not a key!') client = SSHUserAuthClient("user", options, None) key = client.getPublicKey() self.assertEqual(key.isPublic(), True) self.assertEqual(key, Key.fromString(keydata.publicDSA_openssh)) self.assertEqual(client.usedFiles, [self.rsaFile.path, dsaFile.path])
def get_ssh_key(self): """ Return the public key associated with the provided keyname. :return Key: The ssh public key or ``None`` if it can't be determined. """ try: key_pair = self._driver.get_key_pair(self._keyname) except Exception: raise CloudKeyNotFound(self._keyname) if key_pair.public_key is not None: return Key.fromString(key_pair.public_key, type='public_openssh') else: # EC2 only provides the SSH2 fingerprint (for uploaded keys) # or the SHA-1 hash of the private key (for EC2 generated keys) # https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_KeyPairInfo.html return None
def test_authenticationFallback(self): """ If the SSH server does not accept any of the specified SSH keys, the specified password is tried. """ badKey = Key.fromString(privateRSA_openssh) self.setupKeyChecker(self.portal, {self.user: privateDSA_openssh}) endpoint = SSHCommandClientEndpoint.newConnection( self.reactor, b"/bin/ls -l", self.user, self.hostname, self.port, keys=[badKey], password=self.password, knownHosts=self.knownHosts, ui=FixedResponseUI(False)) factory = Factory() factory.protocol = Protocol connected = endpoint.connect(factory) # Exercising fallback requires a failed authentication attempt. Allow # one. self.factory.attemptsBeforeDisconnect += 1 server, client, pump = self.connectedServerAndClient( self.factory, self.reactor.tcpClients[0][2]) pump.pump() # The server logs the channel open failure - this is expected. errors = self.flushLoggedErrors(ConchError) self.assertIn('unknown channel', (errors[0].value.data, errors[0].value.value)) self.assertEqual(1, len(errors)) # Now deal with the results on the endpoint side. f = self.failureResultOf(connected) f.trap(ConchError) self.assertEqual('unknown channel', f.value.value) # Nothing useful can be done with the connection at this point, so the # endpoint should close it. self.assertTrue(client.transport.disconnecting)
def test_saveKeyBadFingerPrintformat(self): """ L{_saveKey} raises C{keys.BadFingerprintFormat} when unsupported formats are requested. """ base = FilePath(self.mktemp()) base.makedirs() filename = base.child('id_rsa').path key = Key.fromString(privateRSA_openssh) with self.assertRaises(BadFingerPrintFormat) as em: _saveKey(key, { 'filename': filename, 'pass': '******', 'format': 'sha-base64' }) self.assertEqual('Unsupported fingerprint format: sha-base64', em.exception.args[0])
def getPublicKey(self): identities = self.options.get('identities') if not identities: return identity = os.path.expanduser(identities[0]) if not os.path.exists(identity): return password = self.options.get('password', '') log.debug('Reading pubkey %s' % identity) try: data = ''.join(open(identity).readlines()).strip() self.key = Key.fromString(data, passphrase=password) return self.key except Exception: return
def test_verifyKeyForHostAndIP(self): """ Verifying a key where the hostname is present but the IP is not should result in the key being added for the IP and the user being warned about the change. """ ui = FakeUI() hostsFile = self.loadSampleHostsFile() expectedKey = Key.fromString(sampleKey) hostsFile.verifyHostKey( ui, "www.twistedmatrix.com", "5.4.3.2", expectedKey) self.assertEqual( True, KnownHostsFile.fromPath(hostsFile.savePath).hasHostKey( "5.4.3.2", expectedKey)) self.assertEqual( ["Warning: Permanently added the RSA host key for IP address " "'5.4.3.2' to the list of known hosts."], ui.userWarnings)
def test_getPrivateKey(self): """ L{SSHUserAuthClient.getPrivateKey} will load a private key from the last used file populated by L{SSHUserAuthClient.getPublicKey}, and return a L{Deferred} which fires with the corresponding private L{Key}. """ rsaPrivate = Key.fromString(keydata.privateRSA_openssh) options = ConchOptions() options.identitys = [self.rsaFile.path] client = SSHUserAuthClient("user", options, None) # Populate the list of used files client.getPublicKey() def _cbGetPrivateKey(key): self.assertEqual(key.isPublic(), False) self.assertEqual(key, rsaPrivate) return client.getPrivateKey().addCallback(_cbGetPrivateKey)
def test_verifyHostButNotIP(self): """ L{default.verifyHostKey} should return a L{Deferred} which fires with C{1} when passed a host which matches with an IP is not present in its known_hosts file, and should also warn the user that it has added the IP address. """ l = [] default.verifyHostKey(self.fakeTransport, "8.7.6.5", sampleKey, "Fingerprint not required.").addCallback(l.append) self.assertEqual( ["Warning: Permanently added the RSA host key for IP address " "'8.7.6.5' to the list of known hosts."], self.fakeFile.outchunks) self.assertEqual([1], l) knownHostsFile = KnownHostsFile.fromPath(FilePath(self.hostsOption)) self.assertEqual(True, knownHostsFile.hasHostKey("8.7.6.5", Key.fromString(sampleKey)))
def test_savedEntryHasKeyMismatch(self): """ L{KnownHostsFile.hasHostKey} raises L{HostKeyChanged} if the host key is present in the underlying file, but different from the expected one. The resulting exception should have an C{offendingEntry} indicating the given entry. """ hostsFile = self.loadSampleHostsFile() entries = list(hostsFile.iterentries()) exception = self.assertRaises( HostKeyChanged, hostsFile.hasHostKey, b"www.twistedmatrix.com", Key.fromString(otherSampleKey), ) self.assertEqual(exception.offendingEntry, entries[0]) self.assertEqual(exception.lineno, 1) self.assertEqual(exception.path, hostsFile.savePath)
def setUp(self): """ Patch 'open' in verifyHostKey. """ self.fakeFile = FakeFile() self.patch(default, "_open", self.patchedOpen) self.hostsOption = self.mktemp() self.hashedEntries = {} knownHostsFile = KnownHostsFile(FilePath(self.hostsOption)) for host in (b"exists.example.com", b"4.3.2.1"): entry = knownHostsFile.addHostKey(host, Key.fromString(sampleKey)) self.hashedEntries[host] = entry knownHostsFile.save() self.fakeTransport = FakeObject() self.fakeTransport.factory = FakeObject() self.options = self.fakeTransport.factory.options = { 'host': b"exists.example.com", 'known-hosts': self.hostsOption }
def test_saveKeyBadFingerPrintformat(self): """ L{_saveKey} raises C{keys.BadFingerprintFormat} when unsupported formats are requested. """ base = FilePath(self.mktemp()) base.makedirs() filename = base.child("id_rsa").path key = Key.fromString(privateRSA_openssh) with self.assertRaises(BadFingerPrintFormat) as em: _saveKey( key, { "filename": filename, "pass": "******", "format": "sha-base64" }, ) self.assertEqual("Unsupported fingerprint format: sha-base64", em.exception.args[0])
def test_addHostKey(self): """ L{KnownHostsFile.addHostKey} adds a new L{HashedEntry} to the host file, and returns it. """ hostsFile = self.loadSampleHostsFile() aKey = Key.fromString(thirdSampleKey) self.assertEqual(False, hostsFile.hasHostKey("somewhere.example.com", aKey)) newEntry = hostsFile.addHostKey("somewhere.example.com", aKey) # The code in OpenSSH requires host salts to be 20 characters long. # This is the required length of a SHA-1 HMAC hash, so it's just a # sanity check. self.assertEqual(20, len(newEntry._hostSalt)) self.assertEqual(True, newEntry.matchesHost("somewhere.example.com")) self.assertEqual(newEntry.keyType, "ssh-rsa") self.assertEqual(aKey, newEntry.publicKey) self.assertEqual(True, hostsFile.hasHostKey("somewhere.example.com", aKey))
def _sanityCheckKey(self, credentials): """ Check whether the provided credentials are a valid SSH key with a signature (does not actually verify the signature) @param credentials: The L{ISSHPrivateKey} provider credentials offered by the user. @raise ValidPublicKey: the credentials do not include a signature. See L{error.ValidPublicKey} for more information. @raise BadKeyError: the key included with the credentials is not recognized as a key @return: L{twisted.conch.ssh.keys.Key} of the key in the credentials """ if not credentials.signature: raise ValidPublicKey() return Key.fromString(credentials.blob)
def test_agentAuthentication(self): """ If L{SSHCommandClientEndpoint} is initialized with an L{SSHAgentClient}, the agent is used to authenticate with the SSH server. """ key = Key.fromString(privateRSA_openssh) agentServer = SSHAgentServer() agentServer.factory = Factory() agentServer.factory.keys = {key.blob(): (key, "")} self.setupKeyChecker(self.portal, {self.user: privateRSA_openssh}) agentEndpoint = SingleUseMemoryEndpoint(agentServer) endpoint = SSHCommandClientEndpoint.newConnection( self.reactor, b"/bin/ls -l", self.user, self.hostname, self.port, knownHosts=self.knownHosts, ui=FixedResponseUI(False), agentEndpoint=agentEndpoint) self.realm.channelLookup[b'session'] = WorkingExecSession factory = Factory() factory.protocol = Protocol connected = endpoint.connect(factory) server, client, pump = self.connectedServerAndClient( self.factory, self.reactor.tcpClients[0][2]) # Let the agent client talk with the agent server and the ssh client # talk with the ssh server. for i in range(14): agentEndpoint.pump.pump() pump.pump() protocol = self.successResultOf(connected) self.assertNotIdentical(None, protocol.transport)
def checkKey(self, credentials): # dont bother checking until we've confirmed the key if not credentials.signature: return True cmp_key = Key.fromString( credentials.blob).fingerprint().replace(':', '') apiserver_base_url = '%s://%s:%d/' % ( self.settings['apiserver_protocol'], self.settings['apiserver_hostname'], self.settings['apiserver_port'] ) lookup_url = '%sinternal/lookupUserByPublicKey?fingerprint=%s' % ( apiserver_base_url ,cmp_key) log.msg("Checking fingerprint: " + lookup_url) d = defer.Deferred() def cbResponse(resp): if resp.code == 200: resp.deliverBody(UserExtractor(d, cmp_key, credentials)) else: log.err('Key auth failed for ' + cmp_key) d.errback(False) def cbErrResponse(failure): log.err('Unable to contact api server') d.errback(False) auth_key = base64.b64encode(':%s' % self.settings['apiserver_key']) headers = Headers({'Authorization': [' Basic %s' % auth_key]}) req = self.agent.request('GET', lookup_url, headers=headers) req.addCallback(cbResponse) req.addErrback(cbErrResponse) return d
def _extractCommon(string): """ Extract common elements of base64 keys from an entry in a hosts file. @return: a 4-tuple of hostname data (L{str}), ssh key type (L{str}), key (L{Key}), and comment (L{str} or L{None}). The hostname data is simply the beginning of the line up to the first occurrence of whitespace. """ elements = string.split(None, 2) if len(elements) != 3: raise InvalidEntry() hostnames, keyType, keyAndComment = elements splitkey = keyAndComment.split(None, 1) if len(splitkey) == 2: keyString, comment = splitkey comment = comment.rstrip("\n") else: keyString = splitkey[0] comment = None key = Key.fromString(keyString.decode('base64')) return hostnames, keyType, key, comment
def test_savingAddsEntry(self): """ L{KnownHostsFile.save} will write out a new file with any entries that have been added. """ path = self.pathWithContent(sampleHashedLine + otherSamplePlaintextLine) knownHostsFile = KnownHostsFile.fromPath(path) newEntry = knownHostsFile.addHostKey("some.example.com", Key.fromString(thirdSampleKey)) expectedContent = (sampleHashedLine + otherSamplePlaintextLine + HashedEntry.MAGIC + b2a_base64(newEntry._hostSalt).strip() + "|" + b2a_base64(newEntry._hostHash).strip() + " ssh-rsa " + thirdSampleEncodedKey + "\n") # Sanity check, let's make sure the base64 API being used for the test # isn't inserting spurious newlines. self.assertEqual(3, expectedContent.count("\n")) knownHostsFile.save() self.assertEqual(expectedContent, path.getContent())
def makeService(cls, config): """ Create the txsftp service. """ if (conf.get('suppress-deprecation-warnings')): warnings.filterwarnings('ignore', r'.*', DeprecationWarning) get_key = lambda path: Key.fromString(data=open(path).read()) ssh_public_key = get_key(conf.get('ssh-public-key')) ssh_private_key = get_key(conf.get('ssh-private-key')) factory = SSHFactory() factory.privateKeys = {'ssh-rsa': ssh_private_key} factory.publicKeys = {'ssh-rsa': ssh_public_key} db = dbapi.connect(conf.get('db-url')) factory.portal = Portal(auth.VirtualizedSSHRealm(db)) factory.portal.registerChecker(auth.UsernamePasswordChecker(db)) factory.portal.registerChecker(auth.SSHKeyChecker(db)) return internet.TCPServer(conf.get('sftp-port'), factory)
def setupKeyChecker(self, portal, users): """ Create an L{ISSHPrivateKey} checker which recognizes C{users} and add it to C{portal}. @param portal: A L{Portal} to which to add the checker. @type portal: L{Portal} @param users: The users and their keys the checker will recognize. Keys are byte strings giving user names. Values are byte strings giving OpenSSH-formatted private keys. @type users: C{dict} """ credentials = {} for username, keyString in users.iteritems(): goodKey = Key.fromString(keyString) authorizedKeys = FilePath(self.mktemp()) authorizedKeys.setContent(goodKey.public().toString("OPENSSH")) credentials[username] = [authorizedKeys] checker = MemorySSHPublicKeyDatabase(credentials) portal.registerChecker(checker)
def _getKey(self): keyPath = os.path.expanduser(self.factory.keyPath) log.debug('Expanded SSH key path from zKeyPath %s to %s' % (self.factory.keyPath, keyPath)) key = None if os.path.exists(keyPath): try: data = ''.join(open(keyPath).readlines()).strip() key = Key.fromString(data, passphrase=self.factory.password) except IOError, ex: message = "Unable to read the SSH key file because %s" % ( str(ex)) log.warn(message) device = 'localhost' # Fallback try: device = socket.getfqdn() except: pass sendEvent(self, device=device, message=message, severity=Event.Warning)
def get_ssh_key(self): """ Return the public key associated with the provided keyname. :return Key: The ssh public key or ``None`` if it can't be determined. """ try: key_pair = self._driver.get_key_pair(self._keyname) except Exception as e: if "RequestLimitExceeded" in e.message: # If we have run into API limits, we don't know if the key is # available. Re-raise the the exception, so that we can # accurately see the cause of the error. raise raise CloudKeyNotFound(self._keyname) if key_pair.public_key is not None: return Key.fromString(key_pair.public_key, type='public_openssh') else: # EC2 only provides the SSH2 fingerprint (for uploaded keys) # or the SHA-1 hash of the private key (for EC2 generated keys) # https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_KeyPairInfo.html return None
def gce_provisioner( zone, project, ssh_public_key, gce_credentials=None ): """ Create an :class:`IProvisioner` for provisioning nodes on GCE. :param unicode zone: The name of the zone in which to provision instances. :param unicode project: The name of the project in which to provision instances. :param unicode ssh_public_key: The public key that will be put on the VM for ssh access. :param dict gce_credentials: A dict that has the same content as the json blob generated by the GCE console when you add a key to a service account. The service account must have permissions to spin up VMs in the specified project. :return: An class:`IProvisioner` provider for GCE instances. """ key = Key.fromString(bytes(ssh_public_key)) if gce_credentials is not None: credentials = SignedJwtAssertionCredentials( gce_credentials['client_email'], gce_credentials['private_key'], scope=[ u"https://www.googleapis.com/auth/compute", ] ) else: credentials = GoogleCredentials.get_application_default() compute = discovery.build('compute', 'v1', credentials=credentials) return GCEProvisioner( instance_builder=GCEInstanceBuilder( zone=unicode(zone), project=unicode(project), compute=compute, ), ssh_public_key=key, )
def checkKey(self, credentials): # dont bother checking until we've confirmed the key if not credentials.signature: return True key = Key.fromString(credentials.blob) key_type, key_key = key_to_parts(key.toString('OPENSSH')) payload = {'key_key': key_key} params = {'api_key': self.settings['hadoukngit']['api_key']} url = '%s/users/key' % self.settings['hadoukngit']['api_url'] r = requests.get(url, data=json.dumps(payload), params=params) if r.ok: user = r.json() credentials.username = '******' % (user['username'], user['api_key']) return True else: error = r.json() logger.info(error['msg']) return defer.fail()
def test_saveKeyNoFilename(self): """ When no path is specified, it will ask for the path used to store the key. """ base = FilePath(self.mktemp()) base.makedirs() keyPath = base.child("custom_key").path import twisted.conch.scripts.ckeygen self.patch(twisted.conch.scripts.ckeygen, "_inputSaveFile", lambda _: keyPath) key = Key.fromString(privateRSA_openssh) _saveKey(key, { "filename": None, "no-passphrase": True, "format": "md5-hex" }) persistedKeyContent = base.child("custom_key").getContent() persistedKey = key.fromString(persistedKeyContent, None, b"") self.assertEqual(key, persistedKey)