1# -*- coding: utf-8 -*- 2# 3# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu> 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# https://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Commandline scripts. 18 19These scripts are called by the executables defined in setup.py. 20""" 21 22from __future__ import with_statement, print_function 23 24import abc 25import sys 26from optparse import OptionParser 27 28import rsa 29import rsa.pkcs1 30 31HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys()) 32 33 34def keygen(): 35 """Key generator.""" 36 37 # Parse the CLI options 38 parser = OptionParser(usage='usage: %prog [options] keysize', 39 description='Generates a new RSA keypair of "keysize" bits.') 40 41 parser.add_option('--pubout', type='string', 42 help='Output filename for the public key. The public key is ' 43 'not saved if this option is not present. You can use ' 44 'pyrsa-priv2pub to create the public key file later.') 45 46 parser.add_option('-o', '--out', type='string', 47 help='Output filename for the private key. The key is ' 48 'written to stdout if this option is not present.') 49 50 parser.add_option('--form', 51 help='key format of the private and public keys - default PEM', 52 choices=('PEM', 'DER'), default='PEM') 53 54 (cli, cli_args) = parser.parse_args(sys.argv[1:]) 55 56 if len(cli_args) != 1: 57 parser.print_help() 58 raise SystemExit(1) 59 60 try: 61 keysize = int(cli_args[0]) 62 except ValueError: 63 parser.print_help() 64 print('Not a valid number: %s' % cli_args[0], file=sys.stderr) 65 raise SystemExit(1) 66 67 print('Generating %i-bit key' % keysize, file=sys.stderr) 68 (pub_key, priv_key) = rsa.newkeys(keysize) 69 70 # Save public key 71 if cli.pubout: 72 print('Writing public key to %s' % cli.pubout, file=sys.stderr) 73 data = pub_key.save_pkcs1(format=cli.form) 74 with open(cli.pubout, 'wb') as outfile: 75 outfile.write(data) 76 77 # Save private key 78 data = priv_key.save_pkcs1(format=cli.form) 79 80 if cli.out: 81 print('Writing private key to %s' % cli.out, file=sys.stderr) 82 with open(cli.out, 'wb') as outfile: 83 outfile.write(data) 84 else: 85 print('Writing private key to stdout', file=sys.stderr) 86 rsa._compat.write_to_stdout(data) 87 88 89class CryptoOperation(object): 90 """CLI callable that operates with input, output, and a key.""" 91 92 __metaclass__ = abc.ABCMeta 93 94 keyname = 'public' # or 'private' 95 usage = 'usage: %%prog [options] %(keyname)s_key' 96 description = None 97 operation = 'decrypt' 98 operation_past = 'decrypted' 99 operation_progressive = 'decrypting' 100 input_help = 'Name of the file to %(operation)s. Reads from stdin if ' \ 101 'not specified.' 102 output_help = 'Name of the file to write the %(operation_past)s file ' \ 103 'to. Written to stdout if this option is not present.' 104 expected_cli_args = 1 105 has_output = True 106 107 key_class = rsa.PublicKey 108 109 def __init__(self): 110 self.usage = self.usage % self.__class__.__dict__ 111 self.input_help = self.input_help % self.__class__.__dict__ 112 self.output_help = self.output_help % self.__class__.__dict__ 113 114 @abc.abstractmethod 115 def perform_operation(self, indata, key, cli_args): 116 """Performs the program's operation. 117 118 Implement in a subclass. 119 120 :returns: the data to write to the output. 121 """ 122 123 def __call__(self): 124 """Runs the program.""" 125 126 (cli, cli_args) = self.parse_cli() 127 128 key = self.read_key(cli_args[0], cli.keyform) 129 130 indata = self.read_infile(cli.input) 131 132 print(self.operation_progressive.title(), file=sys.stderr) 133 outdata = self.perform_operation(indata, key, cli_args) 134 135 if self.has_output: 136 self.write_outfile(outdata, cli.output) 137 138 def parse_cli(self): 139 """Parse the CLI options 140 141 :returns: (cli_opts, cli_args) 142 """ 143 144 parser = OptionParser(usage=self.usage, description=self.description) 145 146 parser.add_option('-i', '--input', type='string', help=self.input_help) 147 148 if self.has_output: 149 parser.add_option('-o', '--output', type='string', help=self.output_help) 150 151 parser.add_option('--keyform', 152 help='Key format of the %s key - default PEM' % self.keyname, 153 choices=('PEM', 'DER'), default='PEM') 154 155 (cli, cli_args) = parser.parse_args(sys.argv[1:]) 156 157 if len(cli_args) != self.expected_cli_args: 158 parser.print_help() 159 raise SystemExit(1) 160 161 return cli, cli_args 162 163 def read_key(self, filename, keyform): 164 """Reads a public or private key.""" 165 166 print('Reading %s key from %s' % (self.keyname, filename), file=sys.stderr) 167 with open(filename, 'rb') as keyfile: 168 keydata = keyfile.read() 169 170 return self.key_class.load_pkcs1(keydata, keyform) 171 172 def read_infile(self, inname): 173 """Read the input file""" 174 175 if inname: 176 print('Reading input from %s' % inname, file=sys.stderr) 177 with open(inname, 'rb') as infile: 178 return infile.read() 179 180 print('Reading input from stdin', file=sys.stderr) 181 return sys.stdin.read() 182 183 def write_outfile(self, outdata, outname): 184 """Write the output file""" 185 186 if outname: 187 print('Writing output to %s' % outname, file=sys.stderr) 188 with open(outname, 'wb') as outfile: 189 outfile.write(outdata) 190 else: 191 print('Writing output to stdout', file=sys.stderr) 192 rsa._compat.write_to_stdout(outdata) 193 194 195class EncryptOperation(CryptoOperation): 196 """Encrypts a file.""" 197 198 keyname = 'public' 199 description = ('Encrypts a file. The file must be shorter than the key ' 200 'length in order to be encrypted.') 201 operation = 'encrypt' 202 operation_past = 'encrypted' 203 operation_progressive = 'encrypting' 204 205 def perform_operation(self, indata, pub_key, cli_args=None): 206 """Encrypts files.""" 207 208 return rsa.encrypt(indata, pub_key) 209 210 211class DecryptOperation(CryptoOperation): 212 """Decrypts a file.""" 213 214 keyname = 'private' 215 description = ('Decrypts a file. The original file must be shorter than ' 216 'the key length in order to have been encrypted.') 217 operation = 'decrypt' 218 operation_past = 'decrypted' 219 operation_progressive = 'decrypting' 220 key_class = rsa.PrivateKey 221 222 def perform_operation(self, indata, priv_key, cli_args=None): 223 """Decrypts files.""" 224 225 return rsa.decrypt(indata, priv_key) 226 227 228class SignOperation(CryptoOperation): 229 """Signs a file.""" 230 231 keyname = 'private' 232 usage = 'usage: %%prog [options] private_key hash_method' 233 description = ('Signs a file, outputs the signature. Choose the hash ' 234 'method from %s' % ', '.join(HASH_METHODS)) 235 operation = 'sign' 236 operation_past = 'signature' 237 operation_progressive = 'Signing' 238 key_class = rsa.PrivateKey 239 expected_cli_args = 2 240 241 output_help = ('Name of the file to write the signature to. Written ' 242 'to stdout if this option is not present.') 243 244 def perform_operation(self, indata, priv_key, cli_args): 245 """Signs files.""" 246 247 hash_method = cli_args[1] 248 if hash_method not in HASH_METHODS: 249 raise SystemExit('Invalid hash method, choose one of %s' % 250 ', '.join(HASH_METHODS)) 251 252 return rsa.sign(indata, priv_key, hash_method) 253 254 255class VerifyOperation(CryptoOperation): 256 """Verify a signature.""" 257 258 keyname = 'public' 259 usage = 'usage: %%prog [options] public_key signature_file' 260 description = ('Verifies a signature, exits with status 0 upon success, ' 261 'prints an error message and exits with status 1 upon error.') 262 operation = 'verify' 263 operation_past = 'verified' 264 operation_progressive = 'Verifying' 265 key_class = rsa.PublicKey 266 expected_cli_args = 2 267 has_output = False 268 269 def perform_operation(self, indata, pub_key, cli_args): 270 """Verifies files.""" 271 272 signature_file = cli_args[1] 273 274 with open(signature_file, 'rb') as sigfile: 275 signature = sigfile.read() 276 277 try: 278 rsa.verify(indata, signature, pub_key) 279 except rsa.VerificationError: 280 raise SystemExit('Verification failed.') 281 282 print('Verification OK', file=sys.stderr) 283 284 285encrypt = EncryptOperation() 286decrypt = DecryptOperation() 287sign = SignOperation() 288verify = VerifyOperation() 289