| 1 | # -*- test-case-name: twisted.conch.test.test_ckeygen -*- |
|---|
| 2 | # Copyright (c) Twisted Matrix Laboratories. |
|---|
| 3 | # See LICENSE for details. |
|---|
| 4 | |
|---|
| 5 | """ |
|---|
| 6 | Implementation module for the `ckeygen` command. |
|---|
| 7 | """ |
|---|
| 8 | |
|---|
| 9 | import sys, os, getpass, socket |
|---|
| 10 | if getpass.getpass == getpass.unix_getpass: |
|---|
| 11 | try: |
|---|
| 12 | import termios # hack around broken termios |
|---|
| 13 | termios.tcgetattr, termios.tcsetattr |
|---|
| 14 | except (ImportError, AttributeError): |
|---|
| 15 | sys.modules['termios'] = None |
|---|
| 16 | reload(getpass) |
|---|
| 17 | |
|---|
| 18 | from twisted.conch.ssh import keys |
|---|
| 19 | from twisted.python import filepath, log, usage, randbytes |
|---|
| 20 | |
|---|
| 21 | |
|---|
| 22 | class GeneralOptions(usage.Options): |
|---|
| 23 | synopsis = """Usage: ckeygen [options] |
|---|
| 24 | """ |
|---|
| 25 | |
|---|
| 26 | longdesc = "ckeygen manipulates public/private keys in various ways." |
|---|
| 27 | |
|---|
| 28 | optParameters = [['bits', 'b', 1024, 'Number of bits in the key to create.'], |
|---|
| 29 | ['filename', 'f', None, 'Filename of the key file.'], |
|---|
| 30 | ['type', 't', None, 'Specify type of key to create.'], |
|---|
| 31 | ['comment', 'C', None, 'Provide new comment.'], |
|---|
| 32 | ['newpass', 'N', None, 'Provide new passphrase.'], |
|---|
| 33 | ['pass', 'P', None, 'Provide old passphrase']] |
|---|
| 34 | |
|---|
| 35 | optFlags = [['fingerprint', 'l', 'Show fingerprint of key file.'], |
|---|
| 36 | ['changepass', 'p', 'Change passphrase of private key file.'], |
|---|
| 37 | ['quiet', 'q', 'Quiet.'], |
|---|
| 38 | ['showpub', 'y', 'Read private key file and print public key.']] |
|---|
| 39 | |
|---|
| 40 | compData = usage.Completions( |
|---|
| 41 | optActions={"type": usage.CompleteList(["rsa", "dsa"])}) |
|---|
| 42 | |
|---|
| 43 | def run(): |
|---|
| 44 | options = GeneralOptions() |
|---|
| 45 | try: |
|---|
| 46 | options.parseOptions(sys.argv[1:]) |
|---|
| 47 | except usage.UsageError, u: |
|---|
| 48 | print 'ERROR: %s' % u |
|---|
| 49 | options.opt_help() |
|---|
| 50 | sys.exit(1) |
|---|
| 51 | log.discardLogs() |
|---|
| 52 | log.deferr = handleError # HACK |
|---|
| 53 | if options['type']: |
|---|
| 54 | if options['type'] == 'rsa': |
|---|
| 55 | generateRSAkey(options) |
|---|
| 56 | elif options['type'] == 'dsa': |
|---|
| 57 | generateDSAkey(options) |
|---|
| 58 | else: |
|---|
| 59 | sys.exit('Key type was %s, must be one of: rsa, dsa' % options['type']) |
|---|
| 60 | elif options['fingerprint']: |
|---|
| 61 | printFingerprint(options) |
|---|
| 62 | elif options['changepass']: |
|---|
| 63 | changePassPhrase(options) |
|---|
| 64 | elif options['showpub']: |
|---|
| 65 | displayPublicKey(options) |
|---|
| 66 | else: |
|---|
| 67 | options.opt_help() |
|---|
| 68 | sys.exit(1) |
|---|
| 69 | |
|---|
| 70 | def handleError(): |
|---|
| 71 | from twisted.python import failure |
|---|
| 72 | global exitStatus |
|---|
| 73 | exitStatus = 2 |
|---|
| 74 | log.err(failure.Failure()) |
|---|
| 75 | reactor.stop() |
|---|
| 76 | raise |
|---|
| 77 | |
|---|
| 78 | def generateRSAkey(options): |
|---|
| 79 | from Crypto.PublicKey import RSA |
|---|
| 80 | print 'Generating public/private rsa key pair.' |
|---|
| 81 | key = RSA.generate(int(options['bits']), randbytes.secureRandom) |
|---|
| 82 | _saveKey(key, options) |
|---|
| 83 | |
|---|
| 84 | def generateDSAkey(options): |
|---|
| 85 | from Crypto.PublicKey import DSA |
|---|
| 86 | print 'Generating public/private dsa key pair.' |
|---|
| 87 | key = DSA.generate(int(options['bits']), randbytes.secureRandom) |
|---|
| 88 | _saveKey(key, options) |
|---|
| 89 | |
|---|
| 90 | |
|---|
| 91 | def printFingerprint(options): |
|---|
| 92 | if not options['filename']: |
|---|
| 93 | filename = os.path.expanduser('~/.ssh/id_rsa') |
|---|
| 94 | options['filename'] = raw_input('Enter file in which the key is (%s): ' % filename) |
|---|
| 95 | if os.path.exists(options['filename']+'.pub'): |
|---|
| 96 | options['filename'] += '.pub' |
|---|
| 97 | try: |
|---|
| 98 | key = keys.Key.fromFile(options['filename']) |
|---|
| 99 | obj = key.keyObject |
|---|
| 100 | string = key.blob() |
|---|
| 101 | print '%s %s %s' % ( |
|---|
| 102 | obj.size() + 1, |
|---|
| 103 | key.fingerprint(), |
|---|
| 104 | os.path.basename(options['filename'])) |
|---|
| 105 | except: |
|---|
| 106 | sys.exit('bad key') |
|---|
| 107 | |
|---|
| 108 | |
|---|
| 109 | def changePassPhrase(options): |
|---|
| 110 | if not options['filename']: |
|---|
| 111 | filename = os.path.expanduser('~/.ssh/id_rsa') |
|---|
| 112 | options['filename'] = raw_input('Enter file in which the key is (%s): ' % filename) |
|---|
| 113 | try: |
|---|
| 114 | key = keys.Key.fromFile(options['filename']).keyObject |
|---|
| 115 | except keys.BadKeyError, e: |
|---|
| 116 | if e.args[0] != 'encrypted key with no passphrase': |
|---|
| 117 | raise |
|---|
| 118 | else: |
|---|
| 119 | if not options['pass']: |
|---|
| 120 | options['pass'] = getpass.getpass('Enter old passphrase: ') |
|---|
| 121 | key = keys.Key.fromFile( |
|---|
| 122 | options['filename'], passphrase = options['pass']).keyObject |
|---|
| 123 | if not options['newpass']: |
|---|
| 124 | while 1: |
|---|
| 125 | p1 = getpass.getpass('Enter new passphrase (empty for no passphrase): ') |
|---|
| 126 | p2 = getpass.getpass('Enter same passphrase again: ') |
|---|
| 127 | if p1 == p2: |
|---|
| 128 | break |
|---|
| 129 | print 'Passphrases do not match. Try again.' |
|---|
| 130 | options['newpass'] = p1 |
|---|
| 131 | open(options['filename'], 'w').write( |
|---|
| 132 | keys.Key(key).toString(passphrase=options['newpass'])) |
|---|
| 133 | print 'Your identification has been saved with the new passphrase.' |
|---|
| 134 | |
|---|
| 135 | |
|---|
| 136 | def displayPublicKey(options): |
|---|
| 137 | if not options['filename']: |
|---|
| 138 | filename = os.path.expanduser('~/.ssh/id_rsa') |
|---|
| 139 | options['filename'] = raw_input('Enter file in which the key is (%s): ' % filename) |
|---|
| 140 | try: |
|---|
| 141 | key = keys.Key.fromFile(options['filename']).keyObject |
|---|
| 142 | except keys.BadKeyError, e: |
|---|
| 143 | if e.args[0] != 'encrypted key with no passphrase': |
|---|
| 144 | raise |
|---|
| 145 | else: |
|---|
| 146 | if not options['pass']: |
|---|
| 147 | options['pass'] = getpass.getpass('Enter passphrase: ') |
|---|
| 148 | key = keys.Key.fromFile( |
|---|
| 149 | options['filename'], passphrase = options['pass']).keyObject |
|---|
| 150 | print keys.Key(key).public().toString() |
|---|
| 151 | |
|---|
| 152 | |
|---|
| 153 | def _saveKey(key, options): |
|---|
| 154 | if not options['filename']: |
|---|
| 155 | kind = keys.objectType(key) |
|---|
| 156 | kind = {'ssh-rsa':'rsa','ssh-dss':'dsa'}[kind] |
|---|
| 157 | filename = os.path.expanduser('~/.ssh/id_%s'%kind) |
|---|
| 158 | options['filename'] = raw_input('Enter file in which to save the key (%s): '%filename).strip() or filename |
|---|
| 159 | if os.path.exists(options['filename']): |
|---|
| 160 | print '%s already exists.' % options['filename'] |
|---|
| 161 | yn = raw_input('Overwrite (y/n)? ') |
|---|
| 162 | if yn[0].lower() != 'y': |
|---|
| 163 | sys.exit() |
|---|
| 164 | if not options['pass']: |
|---|
| 165 | while 1: |
|---|
| 166 | p1 = getpass.getpass('Enter passphrase (empty for no passphrase): ') |
|---|
| 167 | p2 = getpass.getpass('Enter same passphrase again: ') |
|---|
| 168 | if p1 == p2: |
|---|
| 169 | break |
|---|
| 170 | print 'Passphrases do not match. Try again.' |
|---|
| 171 | options['pass'] = p1 |
|---|
| 172 | |
|---|
| 173 | keyObj = keys.Key(key) |
|---|
| 174 | comment = '%s@%s' % (getpass.getuser(), socket.gethostname()) |
|---|
| 175 | |
|---|
| 176 | filepath.FilePath(options['filename']).setContent( |
|---|
| 177 | keyObj.toString('openssh', options['pass'])) |
|---|
| 178 | os.chmod(options['filename'], 33152) |
|---|
| 179 | |
|---|
| 180 | filepath.FilePath(options['filename'] + '.pub').setContent( |
|---|
| 181 | keyObj.public().toString('openssh', comment)) |
|---|
| 182 | |
|---|
| 183 | print 'Your identification has been saved in %s' % options['filename'] |
|---|
| 184 | print 'Your public key has been saved in %s.pub' % options['filename'] |
|---|
| 185 | print 'The key fingerprint is:' |
|---|
| 186 | print keyObj.fingerprint() |
|---|
| 187 | |
|---|
| 188 | if __name__ == '__main__': |
|---|
| 189 | run() |
|---|