diff --git a/.travis.yml b/.travis.yml index 62e9198582b..499d71107c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -78,6 +78,7 @@ matrix: # twistedchecker job was introduce as an experimental job. # Once it is stable we can enforce it - env: TOXENV=txchecker-travis + - osx_image: xcode7.1 addons: diff --git a/src/twisted/conch/scripts/ckeygen.py b/src/twisted/conch/scripts/ckeygen.py index 3cd27c8a340..d1689cc4bc5 100644 --- a/src/twisted/conch/scripts/ckeygen.py +++ b/src/twisted/conch/scripts/ckeygen.py @@ -19,6 +19,7 @@ from twisted.conch.ssh import keys from twisted.python import failure, filepath, log, usage +from twisted.python.compat import raw_input, _PY3 @@ -184,7 +185,7 @@ def changePassPhrase(options): except (keys.EncryptedKeyError, keys.BadKeyError) as e: sys.exit('Could not change passphrase: %s' % (e,)) - with open(options['filename'], 'w') as fd: + with open(options['filename'], 'wb') as fd: fd.write(newkeydata) print('Your identification has been saved with the new passphrase.') @@ -202,7 +203,10 @@ def displayPublicKey(options): options['pass'] = getpass.getpass('Enter passphrase: ') key = keys.Key.fromFile( options['filename'], passphrase = options['pass']) - print(key.public().toString('openssh')) + displayKey = key.public().toString('openssh') + if _PY3: + displayKey = displayKey.decode("ascii") + print(displayKey) diff --git a/src/twisted/conch/ssh/keys.py b/src/twisted/conch/ssh/keys.py index 71b2f0b469a..4831a131821 100644 --- a/src/twisted/conch/ssh/keys.py +++ b/src/twisted/conch/ssh/keys.py @@ -39,7 +39,7 @@ from twisted.conch.ssh.common import int_from_bytes, int_to_bytes from twisted.python import randbytes from twisted.python.compat import ( - iterbytes, long, izip, nativeString, _PY3, + iterbytes, long, izip, nativeString, unicode, _PY3, _b64decodebytes as decodebytes, _b64encodebytes as encodebytes) from twisted.python.constants import NamedConstant, Names from twisted.python.deprecate import deprecated, getDeprecationWarningString @@ -144,6 +144,10 @@ def fromString(cls, data, type=None, passphrase=None): @rtype: L{Key} @return: The loaded key. """ + if isinstance(data, unicode): + data = data.encode("utf-8") + if isinstance(passphrase, unicode): + passphrase = passphrase.encode("utf-8") if type is None: type = cls._guessStringType(data) if type is None: @@ -1024,10 +1028,12 @@ def toString(self, type, extra=None): is not part of the key itself. For public OpenSSH keys, this is a comment. For private OpenSSH keys, this is a passphrase to encrypt with. - @type extra: L{bytes} or L{None} + @type extra: L{bytes} or L{unicode} or L{None} @rtype: L{bytes} """ + if isinstance(extra, unicode): + extra = extra.encode("utf-8") method = getattr(self, '_toString_%s' % (type.upper(),), None) if method is None: raise BadKeyError('unknown key type: %s' % (type,)) diff --git a/src/twisted/conch/test/test_ckeygen.py b/src/twisted/conch/test/test_ckeygen.py index d4273c46a61..3b5ada2540d 100644 --- a/src/twisted/conch/test/test_ckeygen.py +++ b/src/twisted/conch/test/test_ckeygen.py @@ -5,11 +5,12 @@ Tests for L{twisted.conch.scripts.ckeygen}. """ -import __builtin__ import getpass import sys -from StringIO import StringIO +from io import BytesIO, StringIO + +from twisted.python.compat import unicode, _PY3 from twisted.python.reflect import requireModule if requireModule('cryptography') and requireModule('pyasn1'): @@ -41,7 +42,7 @@ def makeGetpass(*passphrases): passphrases = iter(passphrases) def fakeGetpass(_): - return passphrases.next() + return next(passphrases) return fakeGetpass @@ -53,10 +54,12 @@ class KeyGenTests(TestCase): """ def setUp(self): """ - Patch C{sys.stdout} with a L{StringIO} instance to tests can make - assertions about what's printed. + Patch C{sys.stdout} so tests can make assertions about what's printed. """ - self.stdout = StringIO() + if _PY3: + self.stdout = StringIO() + else: + self.stdout = BytesIO() self.patch(sys, 'stdout', self.stdout) @@ -231,7 +234,8 @@ def test_saveKeyNoFilename(self): base.makedirs() keyPath = base.child('custom_key').path - self.patch(__builtin__, 'raw_input', lambda _: keyPath) + import twisted.conch.scripts.ckeygen + self.patch(twisted.conch.scripts.ckeygen, 'raw_input', lambda _: keyPath) key = Key.fromString(privateRSA_openssh) _saveKey(key, {'filename': None, 'no-passphrase': True, 'format': 'md5-hex'}) @@ -250,8 +254,11 @@ def test_displayPublicKey(self): pubKey = Key.fromString(publicRSA_openssh) FilePath(filename).setContent(privateRSA_openssh) displayPublicKey({'filename': filename}) + displayed = self.stdout.getvalue().strip('\n') + if isinstance(displayed, unicode): + displayed = displayed.encode("ascii") self.assertEqual( - self.stdout.getvalue().strip('\n'), + displayed, pubKey.toString('openssh')) @@ -264,8 +271,11 @@ def test_displayPublicKeyEncrypted(self): pubKey = Key.fromString(publicRSA_openssh) FilePath(filename).setContent(privateRSA_openssh_encrypted) displayPublicKey({'filename': filename, 'pass': 'encrypted'}) + displayed = self.stdout.getvalue().strip('\n') + if isinstance(displayed, unicode): + displayed = displayed.encode("ascii") self.assertEqual( - self.stdout.getvalue().strip('\n'), + displayed, pubKey.toString('openssh')) @@ -279,8 +289,11 @@ def test_displayPublicKeyEncryptedPassphrasePrompt(self): 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( - self.stdout.getvalue().strip('\n'), + displayed, pubKey.toString('openssh')) @@ -392,13 +405,16 @@ def test_changePassphraseBadKey(self): key. """ filename = self.mktemp() - FilePath(filename).setContent('foobar') + FilePath(filename).setContent(b'foobar') error = self.assertRaises( SystemExit, changePassPhrase, {'filename': filename}) - self.assertEqual( - "Could not change passphrase: cannot guess the type of 'foobar'", - str(error)) - self.assertEqual('foobar', FilePath(filename).getContent()) + + if _PY3: + expected = "Could not change passphrase: cannot guess the type of b'foobar'" + else: + expected = "Could not change passphrase: cannot guess the type of 'foobar'" + self.assertEqual(expected, str(error)) + self.assertEqual(b'foobar', FilePath(filename).getContent()) def test_changePassphraseCreateError(self): @@ -442,9 +458,13 @@ def toString(*args, **kwargs): SystemExit, changePassPhrase, {'filename': filename, 'newpass': 'newencrypt'}) - self.assertEqual( - "Could not change passphrase: " - "cannot guess the type of ''", str(error)) + if _PY3: + expected = ( + "Could not change passphrase: cannot guess the type of b''") + else: + expected = ( + "Could not change passphrase: cannot guess the type of ''") + self.assertEqual(expected, str(error)) self.assertEqual(privateRSA_openssh, FilePath(filename).getContent()) diff --git a/src/twisted/conch/topfiles/8855.feature b/src/twisted/conch/topfiles/8855.feature new file mode 100644 index 00000000000..3fbec6ea936 --- /dev/null +++ b/src/twisted/conch/topfiles/8855.feature @@ -0,0 +1 @@ +ckeygen has been ported to Python 3 diff --git a/src/twisted/python/_setup.py b/src/twisted/python/_setup.py index 71585072a9a..ce7ef746b7c 100644 --- a/src/twisted/python/_setup.py +++ b/src/twisted/python/_setup.py @@ -128,6 +128,7 @@ # Scripts provided by Twisted on Python 2 and 3. _CONSOLE_SCRIPTS = [ + "ckeygen = twisted.conch.scripts.ckeygen:run", "trial = twisted.scripts.trial:run", "twist = twisted.application.twist._twist:Twist.main", "twistd = twisted.scripts.twistd:run", @@ -135,7 +136,6 @@ # Scripts provided by Twisted on Python 2 only. _CONSOLE_SCRIPTS_PY2 = [ "cftp = twisted.conch.scripts.cftp:run", - "ckeygen = twisted.conch.scripts.ckeygen:run", "conch = twisted.conch.scripts.conch:run", "mailmail = twisted.mail.scripts.mailmail:run", "pyhtmlizer = twisted.scripts.htmlizer:run", @@ -361,7 +361,6 @@ def _checkCPython(sys=sys, platform=platform): "twisted.conch.client.connect", "twisted.conch.client.direct", "twisted.conch.test.test_cftp", - "twisted.conch.test.test_ckeygen", "twisted.conch.test.test_conch", "twisted.conch.test.test_manhole", "twisted.conch.ui.__init__",