Ticket #5894: 5894-ckeygen.2.diff

File 5894-ckeygen.2.diff, 11.1 KB (added by Lucas Taylor, 9 years ago)

Updated patch to apply cleanly against r35757

  • twisted/conch/test/test_keys.py

     
    309309SEJVJ+gmTKdRLYORJKyqhDet6g7kAxs4EoJ25WsOnX5nNr00rit+NkMPA7xbJT+7
    310310CfI51GQLw7pUPeO2WNt6yZO/YkzZrqvTj5FEwybkUyBv7L0gkqu9wjfDdUw0fVHE
    311311xEm4DxjEoaIp8dW/JOzXQ2EF+WaSOgdYsw3Ac+rnnjnNptCdOEDGP6QBkt+oXj4P
    312 -----END RSA PRIVATE KEY-----""")
     312-----END RSA PRIVATE KEY-----""", passphrase='encrypted')
    313313        # key with invalid encryption type
    314314        self.assertRaises(
    315315            keys.BadKeyError, keys.Key.fromString,
     
    342342SEJVJ+gmTKdRLYORJKyqhDet6g7kAxs4EoJ25WsOnX5nNr00rit+NkMPA7xbJT+7
    343343CfI51GQLw7pUPeO2WNt6yZO/YkzZrqvTj5FEwybkUyBv7L0gkqu9wjfDdUw0fVHE
    344344xEm4DxjEoaIp8dW/JOzXQ2EF+WaSOgdYsw3Ac+rnnjnNptCdOEDGP6QBkt+oXj4P
    345 -----END RSA PRIVATE KEY-----""")
     345-----END RSA PRIVATE KEY-----""", passphrase='encrypted')
    346346        # key with bad IV (AES)
    347347        self.assertRaises(
    348348            keys.BadKeyError, keys.Key.fromString,
     
    375375SEJVJ+gmTKdRLYORJKyqhDet6g7kAxs4EoJ25WsOnX5nNr00rit+NkMPA7xbJT+7
    376376CfI51GQLw7pUPeO2WNt6yZO/YkzZrqvTj5FEwybkUyBv7L0gkqu9wjfDdUw0fVHE
    377377xEm4DxjEoaIp8dW/JOzXQ2EF+WaSOgdYsw3Ac+rnnjnNptCdOEDGP6QBkt+oXj4P
    378 -----END RSA PRIVATE KEY-----""")
     378-----END RSA PRIVATE KEY-----""", passphrase='encrypted')
    379379        # key with bad IV (DES3)
    380380        self.assertRaises(
    381381            keys.BadKeyError, keys.Key.fromString,
     
    408408SEJVJ+gmTKdRLYORJKyqhDet6g7kAxs4EoJ25WsOnX5nNr00rit+NkMPA7xbJT+7
    409409CfI51GQLw7pUPeO2WNt6yZO/YkzZrqvTj5FEwybkUyBv7L0gkqu9wjfDdUw0fVHE
    410410xEm4DxjEoaIp8dW/JOzXQ2EF+WaSOgdYsw3Ac+rnnjnNptCdOEDGP6QBkt+oXj4P
    411 -----END RSA PRIVATE KEY-----""")
     411-----END RSA PRIVATE KEY-----""", passphrase='encrypted')
    412412
    413413    def test_fromFile(self):
    414414        """
  • twisted/conch/test/test_ckeygen.py

     
    1717else:
    1818    from twisted.conch.ssh.keys import Key, BadKeyError
    1919    from twisted.conch.scripts.ckeygen import (
    20         displayPublicKey, printFingerprint, _saveKey)
     20        changePassPhrase, displayPublicKey, printFingerprint, _saveKey)
    2121
    2222from twisted.python.filepath import FilePath
    2323from twisted.trial.unittest import TestCase
     
    2525    publicRSA_openssh, privateRSA_openssh, privateRSA_openssh_encrypted)
    2626
    2727
     28def _make_getpass(*passphrases):
     29    """Return a callable to patch C{getpass.getpass}.
     30    Yields a passphrase each time called. Use case is to provide an
     31    old, then new passphrase(s) as if requested interactively.
     32    """
     33    def _getpass():
     34        yield None
     35        for phrase in passphrases:
     36            yield phrase
     37    getpass = _getpass()
     38    next(getpass)
     39    return getpass.send
    2840
     41
    2942class KeyGenTests(TestCase):
    3043    """
    3144    Tests for various functions used to implement the I{ckeygen} script.
     
    135148        self.assertRaises(
    136149            BadKeyError, displayPublicKey,
    137150            {'filename': filename, 'pass': 'wrong'})
     151
     152
     153    def test_changePassphrase(self):
     154        """
     155        L{changePassPhrase} allows a user to change the passphrase of a
     156        private key interactively.
     157        """
     158        oldNewConfirm = _make_getpass('encrypted', 'newpass', 'newpass')
     159        self.patch(getpass, 'getpass', oldNewConfirm)
     160
     161        filename = self.mktemp()
     162        FilePath(filename).setContent(privateRSA_openssh_encrypted)
     163
     164        changePassPhrase({'filename':filename})
     165        self.assertEqual(
     166            self.stdout.getvalue().strip('\n'),
     167            'Your identification has been saved with the new passphrase.'
     168        )
     169
     170
     171    def test_changePassphraseWithOld(self):
     172        """
     173        L{changePassPhrase} allows a user to change the passphrase of a
     174        private key, providing the old passphrase and prompting for new one.
     175        """
     176        newConfirm = _make_getpass('newpass', 'newpass')
     177        self.patch(getpass, 'getpass', newConfirm)
     178
     179        filename = self.mktemp()
     180        FilePath(filename).setContent(privateRSA_openssh_encrypted)
     181
     182        changePassPhrase({'filename':filename, 'pass':'encrypted'})
     183        self.assertEqual(
     184            self.stdout.getvalue().strip('\n'),
     185            'Your identification has been saved with the new passphrase.'
     186        )
     187
     188
     189    def test_changePassphraseWithBoth(self):
     190        """
     191        L{changePassPhrase} allows a user to change the passphrase of a
     192        private key by providing both old and new passphrases w/o prompting.
     193        """
     194        filename = self.mktemp()
     195        FilePath(filename).setContent(privateRSA_openssh_encrypted)
     196
     197        changePassPhrase(
     198            {'filename':filename, 'pass':'encrypted', 'newpass':'newencrypt'})
     199        self.assertEqual(
     200            self.stdout.getvalue().strip('\n'),
     201            'Your identification has been saved with the new passphrase.'
     202        )
     203
     204
     205    def test_changePassphraseWrongPassphrase(self):
     206        """
     207        L{changePassPhrase} allows a user to change the passphrase of a
     208        private key. It should exit if passed an invalid old passphrase.
     209        """
     210        filename = self.mktemp()
     211        FilePath(filename).setContent(privateRSA_openssh_encrypted)
     212        self.assertRaises(SystemExit,
     213                          changePassPhrase,
     214                          {'filename':filename, 'pass':'wrong'})
  • twisted/conch/scripts/ckeygen.py

     
    118118
    119119
    120120def changePassPhrase(options):
    121     if not options['filename']:
     121    if not options.get('filename'):
    122122        filename = os.path.expanduser('~/.ssh/id_rsa')
    123123        options['filename'] = raw_input('Enter file in which the key is (%s): ' % filename)
    124124    try:
    125125        key = keys.Key.fromFile(options['filename']).keyObject
     126    except keys.EncryptedKeyError, e:
     127        # Raised if password not supplied for an encrypted key
     128        if not options.get('pass'):
     129            options['pass'] = getpass.getpass('Enter old passphrase: ')
     130        try:
     131            key = keys.Key.fromFile(
     132                options['filename'], passphrase=options['pass']).keyObject
     133        except keys.BadKeyError, e:
     134            sys.exit('Could not change passphrase: Old passphrase error')
     135        except keys.EncryptedKeyError, e:
     136            sys.exit('Could not change passphrase: %s' % (e,))
    126137    except keys.BadKeyError, e:
    127         if e.args[0] != 'encrypted key with no passphrase':
    128             raise
    129         else:
    130             if not options['pass']:
    131                 options['pass'] = getpass.getpass('Enter old passphrase: ')
    132             key = keys.Key.fromFile(
    133                 options['filename'], passphrase = options['pass']).keyObject
    134     if not options['newpass']:
     138        sys.exit('Could not change passphrase: %s' % (e,))
     139
     140    if not options.get('newpass'):
    135141        while 1:
    136142            p1 = getpass.getpass('Enter new passphrase (empty for no passphrase): ')
    137143            p2 = getpass.getpass('Enter same passphrase again: ')
     
    139145                break
    140146            print 'Passphrases do not match.  Try again.'
    141147        options['newpass'] = p1
    142     open(options['filename'], 'w').write(
    143         keys.Key(key).toString(passphrase=options['newpass']))
    144     print 'Your identification has been saved with the new passphrase.'
    145148
     149    try:
     150        newkeydata = keys.Key(key).toString('openssh', extra=options['newpass'])
     151    except (keys.BadKeyError, Exception), e:
     152        sys.exit('Could not change passphrase: %s' % (e,))
     153    else:
     154        open(options['filename'], 'w').write(newkeydata)
    146155
     156    try:
     157        newkey = keys.Key.fromFile(
     158            options['filename'], passphrase=options['newpass']).keyObject
     159    except (keys.EncryptedKeyError, keys.BadKeyError), e:
     160        sys.exit('Could not change passphrase: %s' % (e,))
     161    else:
     162        print 'Your identification has been saved with the new passphrase.'
    147163
     164
     165
    148166def displayPublicKey(options):
    149167    if not options['filename']:
    150168        filename = os.path.expanduser('~/.ssh/id_rsa')
  • twisted/conch/ssh/keys.py

     
    1616from Crypto.Cipher import DES3, AES
    1717from Crypto.PublicKey import RSA, DSA
    1818from Crypto import Util
     19from pyasn1.error import PyAsn1Error
    1920from pyasn1.type import univ
    2021from pyasn1.codec.ber import decoder as berDecoder
    2122from pyasn1.codec.ber import encoder as berEncoder
     
    214215        lines = data.strip().split('\n')
    215216        kind = lines[0][11:14]
    216217        if lines[1].startswith('Proc-Type: 4,ENCRYPTED'):  # encrypted key
     218            if not passphrase:
     219                raise EncryptedKeyError('Passphrase must be provided '
     220                                        'for an encrypted key')
     221
     222            # Determine cipher and initialization vector
    217223            try:
    218224                _, cipher_iv_info = lines[2].split(' ', 1)
    219225                cipher, ivdata = cipher_iv_info.rstrip().split(',', 1)
    220226            except ValueError:
    221227                raise BadKeyError('invalid DEK-info %r' % lines[2])
     228
    222229            if cipher == 'AES-128-CBC':
    223230                CipherClass = AES
    224231                keySize = 16
     
    231238                    raise BadKeyError('DES encrypted key with a bad IV')
    232239            else:
    233240                raise BadKeyError('unknown encryption type %r' % cipher)
     241
     242            # extract keyData for decoding
    234243            iv = ''.join([chr(int(ivdata[i:i + 2], 16))
    235244                          for i in range(0, len(ivdata), 2)])
    236             if not passphrase:
    237                 raise EncryptedKeyError('encrypted key with no passphrase')
    238245            ba = md5(passphrase + iv[:8]).digest()
    239246            bb = md5(ba + passphrase + iv[:8]).digest()
    240247            decKey = (ba + bb)[:keySize]
     
    247254        else:
    248255            b64Data = ''.join(lines[1:-1])
    249256            keyData = base64.decodestring(b64Data)
     257
    250258        try:
    251259            decodedKey = berDecoder.decode(keyData)[0]
    252         except Exception:
    253             raise BadKeyError('Failed to decode key')
     260        except PyAsn1Error, e:
     261            raise BadKeyError('Failed to decode key (Bad Passphrase?): %s' % e)
     262
    254263        if kind == 'RSA':
    255264            if len(decodedKey) == 2:  # alternate RSA key
    256265                decodedKey = decodedKey[0]
    257266            if len(decodedKey) < 6:
    258267                raise BadKeyError('RSA key failed to decode properly')
     268
    259269            n, e, d, p, q = [long(value) for value in decodedKey[1:6]]
    260270            if p > q:  # make p smaller than q
    261271                p, q = q, p
     
    633643        string formats.  If extra is present, it represents a comment for a
    634644        public key, or a passphrase for a private key.
    635645
    636         @type extra: C{str}
     646        @type extra: C{str} Comment for a public key or
     647                            Passphrase for a private key
    637648        @rtype: C{str}
    638649        """
    639650        data = self.data()