• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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