blob: c7a24f43e2aa4d870cfdbcbc2dd257198a09cf12 [file] [log] [blame]
Sybren A. Stüvel64c5f922011-07-31 19:32:071# Copyright 2011 Sybren A. Stüvel <[email protected]>
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
Sybren A. Stüvel3934ab42016-02-05 15:01:207# https://www.apache.org/licenses/LICENSE-2.0
Sybren A. Stüvel64c5f922011-07-31 19:32:078#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Sybren A. Stüveld3d10342016-01-22 10:36:0615"""Commandline scripts.
Sybren A. Stüvel64c5f922011-07-31 19:32:0716
Sybren A. Stüvelcca6be62011-07-31 19:34:2717These scripts are called by the executables defined in setup.py.
Sybren A. Stüveld3d10342016-01-22 10:36:0618"""
Yesudeep Mangalapilly03c51e72011-08-16 09:00:4819
Sybren A. Stüvel1d15b0b2011-07-31 20:30:1820import abc
Sybren A. Stüvel64c5f922011-07-31 19:32:0721import sys
Sybren A. Stüvel6760eb72019-08-04 13:47:1122import typing
Sybren A. Stüvelb6cebd52019-08-04 14:41:0123import optparse
Sybren A. Stüvel64c5f922011-07-31 19:32:0724
25import rsa
Sybren A. Stüvel6760eb72019-08-04 13:47:1126import rsa.key
Sybren A. Stüvel85296722011-07-31 20:54:5127import rsa.pkcs1
28
29HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys())
Sybren A. Stüvelb6cebd52019-08-04 14:41:0130Indexable = typing.Union[typing.Tuple, typing.List[str]]
Sybren A. Stüvel64c5f922011-07-31 19:32:0731
Sybren A. Stüveld3d10342016-01-22 10:36:0632
Sybren A. Stüvelb6cebd52019-08-04 14:41:0133def keygen() -> None:
Sybren A. Stüveld3d10342016-01-22 10:36:0634 """Key generator."""
Sybren A. Stüvelcca6be62011-07-31 19:34:2735
Sybren A. Stüvel64c5f922011-07-31 19:32:0736 # Parse the CLI options
Sybren A. Stüvelb6cebd52019-08-04 14:41:0137 parser = optparse.OptionParser(usage='usage: %prog [options] keysize',
Sybren A. Stüvelf2c3b4f2019-08-04 16:05:2338 description='Generates a new RSA keypair of "keysize" bits.')
Sybren A. Stüveld3d10342016-01-22 10:36:0639
Sybren A. Stüvel64c5f922011-07-31 19:32:0740 parser.add_option('--pubout', type='string',
Sybren A. Stüveld3d10342016-01-22 10:36:0641 help='Output filename for the public key. The public key is '
42 'not saved if this option is not present. You can use '
43 'pyrsa-priv2pub to create the public key file later.')
44
Sybren A. Stüvel58fe9462011-08-03 11:56:3245 parser.add_option('-o', '--out', type='string',
Sybren A. Stüveld3d10342016-01-22 10:36:0646 help='Output filename for the private key. The key is '
47 'written to stdout if this option is not present.')
Sybren A. Stüvel64c5f922011-07-31 19:32:0748
49 parser.add_option('--form',
Sybren A. Stüveld3d10342016-01-22 10:36:0650 help='key format of the private and public keys - default PEM',
51 choices=('PEM', 'DER'), default='PEM')
Sybren A. Stüvel64c5f922011-07-31 19:32:0752
53 (cli, cli_args) = parser.parse_args(sys.argv[1:])
54
55 if len(cli_args) != 1:
56 parser.print_help()
57 raise SystemExit(1)
Sybren A. Stüveld3d10342016-01-22 10:36:0658
Sybren A. Stüvel64c5f922011-07-31 19:32:0759 try:
60 keysize = int(cli_args[0])
Ram Rachum1a5b2d12020-06-19 20:39:0061 except ValueError as ex:
Sybren A. Stüvel64c5f922011-07-31 19:32:0762 parser.print_help()
Sybren A. Stüvelad727272012-06-18 14:14:3763 print('Not a valid number: %s' % cli_args[0], file=sys.stderr)
Ram Rachum1a5b2d12020-06-19 20:39:0064 raise SystemExit(1) from ex
Sybren A. Stüvel64c5f922011-07-31 19:32:0765
Sybren A. Stüvelad727272012-06-18 14:14:3766 print('Generating %i-bit key' % keysize, file=sys.stderr)
Sybren A. Stüvel64c5f922011-07-31 19:32:0767 (pub_key, priv_key) = rsa.newkeys(keysize)
68
Sybren A. Stüvel64c5f922011-07-31 19:32:0769 # Save public key
70 if cli.pubout:
Sybren A. Stüvelad727272012-06-18 14:14:3771 print('Writing public key to %s' % cli.pubout, file=sys.stderr)
Sybren A. Stüvel64c5f922011-07-31 19:32:0772 data = pub_key.save_pkcs1(format=cli.form)
Sybren A. Stüvelad727272012-06-18 14:14:3773 with open(cli.pubout, 'wb') as outfile:
Sybren A. Stüvel64c5f922011-07-31 19:32:0774 outfile.write(data)
75
76 # Save private key
77 data = priv_key.save_pkcs1(format=cli.form)
Sybren A. Stüveld3d10342016-01-22 10:36:0678
Sybren A. Stüvelfc9c7862011-08-03 11:31:4779 if cli.out:
Sybren A. Stüvelad727272012-06-18 14:14:3780 print('Writing private key to %s' % cli.out, file=sys.stderr)
81 with open(cli.out, 'wb') as outfile:
Sybren A. Stüvel64c5f922011-07-31 19:32:0782 outfile.write(data)
83 else:
Sybren A. Stüvelad727272012-06-18 14:14:3784 print('Writing private key to stdout', file=sys.stderr)
Sybren A. Stüvelded036c2019-08-04 13:02:2085 sys.stdout.buffer.write(data)
Sybren A. Stüvel64c5f922011-07-31 19:32:0786
Sybren A. Stüvel1d15b0b2011-07-31 20:30:1887
Sybren A. Stüvel6760eb72019-08-04 13:47:1188class CryptoOperation(metaclass=abc.ABCMeta):
Sybren A. Stüveld3d10342016-01-22 10:36:0689 """CLI callable that operates with input, output, and a key."""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:1890
Sybren A. Stüveld3d10342016-01-22 10:36:0691 keyname = 'public' # or 'private'
Sybren A. Stüvel1d15b0b2011-07-31 20:30:1892 usage = 'usage: %%prog [options] %(keyname)s_key'
Sybren A. Stüvel6760eb72019-08-04 13:47:1193 description = ''
Sybren A. Stüvel1d15b0b2011-07-31 20:30:1894 operation = 'decrypt'
95 operation_past = 'decrypted'
96 operation_progressive = 'decrypting'
97 input_help = 'Name of the file to %(operation)s. Reads from stdin if ' \
Sybren A. Stüveld3d10342016-01-22 10:36:0698 'not specified.'
Sybren A. Stüvel1d15b0b2011-07-31 20:30:1899 output_help = 'Name of the file to write the %(operation_past)s file ' \
Sybren A. Stüveld3d10342016-01-22 10:36:06100 'to. Written to stdout if this option is not present.'
Sybren A. Stüvel85296722011-07-31 20:54:51101 expected_cli_args = 1
102 has_output = True
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18103
Sybren A. Stüvel6760eb72019-08-04 13:47:11104 key_class = rsa.PublicKey # type: typing.Type[rsa.key.AbstractKey]
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18105
Sybren A. Stüvelb6cebd52019-08-04 14:41:01106 def __init__(self) -> None:
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18107 self.usage = self.usage % self.__class__.__dict__
108 self.input_help = self.input_help % self.__class__.__dict__
109 self.output_help = self.output_help % self.__class__.__dict__
110
111 @abc.abstractmethod
Sybren A. Stüvelb6cebd52019-08-04 14:41:01112 def perform_operation(self, indata: bytes, key: rsa.key.AbstractKey,
Andrey Semakinae1a9062019-11-04 13:35:23113 cli_args: Indexable) -> typing.Any:
Sybren A. Stüveld3d10342016-01-22 10:36:06114 """Performs the program's operation.
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18115
116 Implement in a subclass.
117
118 :returns: the data to write to the output.
Sybren A. Stüveld3d10342016-01-22 10:36:06119 """
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18120
Sybren A. Stüvelb6cebd52019-08-04 14:41:01121 def __call__(self) -> None:
Sybren A. Stüveld3d10342016-01-22 10:36:06122 """Runs the program."""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18123
124 (cli, cli_args) = self.parse_cli()
125
126 key = self.read_key(cli_args[0], cli.keyform)
127
128 indata = self.read_infile(cli.input)
129
Sybren A. Stüvelad727272012-06-18 14:14:37130 print(self.operation_progressive.title(), file=sys.stderr)
Sybren A. Stüvel85296722011-07-31 20:54:51131 outdata = self.perform_operation(indata, key, cli_args)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18132
Sybren A. Stüvel85296722011-07-31 20:54:51133 if self.has_output:
134 self.write_outfile(outdata, cli.output)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18135
Sybren A. Stüvelb6cebd52019-08-04 14:41:01136 def parse_cli(self) -> typing.Tuple[optparse.Values, typing.List[str]]:
Sybren A. Stüveld3d10342016-01-22 10:36:06137 """Parse the CLI options
138
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18139 :returns: (cli_opts, cli_args)
Sybren A. Stüveld3d10342016-01-22 10:36:06140 """
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18141
Sybren A. Stüvelb6cebd52019-08-04 14:41:01142 parser = optparse.OptionParser(usage=self.usage, description=self.description)
Sybren A. Stüveld3d10342016-01-22 10:36:06143
Sybren A. Stüvel58fe9462011-08-03 11:56:32144 parser.add_option('-i', '--input', type='string', help=self.input_help)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18145
Sybren A. Stüvel85296722011-07-31 20:54:51146 if self.has_output:
Sybren A. Stüvel58fe9462011-08-03 11:56:32147 parser.add_option('-o', '--output', type='string', help=self.output_help)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18148
149 parser.add_option('--keyform',
Sybren A. Stüveld3d10342016-01-22 10:36:06150 help='Key format of the %s key - default PEM' % self.keyname,
151 choices=('PEM', 'DER'), default='PEM')
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18152
153 (cli, cli_args) = parser.parse_args(sys.argv[1:])
154
Sybren A. Stüvel85296722011-07-31 20:54:51155 if len(cli_args) != self.expected_cli_args:
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18156 parser.print_help()
157 raise SystemExit(1)
158
Sybren A. Stüveld3d10342016-01-22 10:36:06159 return cli, cli_args
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18160
Sybren A. Stüvelb6cebd52019-08-04 14:41:01161 def read_key(self, filename: str, keyform: str) -> rsa.key.AbstractKey:
Sybren A. Stüveld3d10342016-01-22 10:36:06162 """Reads a public or private key."""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18163
Sybren A. Stüvelad727272012-06-18 14:14:37164 print('Reading %s key from %s' % (self.keyname, filename), file=sys.stderr)
165 with open(filename, 'rb') as keyfile:
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18166 keydata = keyfile.read()
167
168 return self.key_class.load_pkcs1(keydata, keyform)
Sybren A. Stüveld3d10342016-01-22 10:36:06169
Sybren A. Stüvelb6cebd52019-08-04 14:41:01170 def read_infile(self, inname: str) -> bytes:
Sybren A. Stüveld3d10342016-01-22 10:36:06171 """Read the input file"""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18172
173 if inname:
Sybren A. Stüvelad727272012-06-18 14:14:37174 print('Reading input from %s' % inname, file=sys.stderr)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18175 with open(inname, 'rb') as infile:
176 return infile.read()
177
Sybren A. Stüvelad727272012-06-18 14:14:37178 print('Reading input from stdin', file=sys.stderr)
Sybren A. Stüvelb6cebd52019-08-04 14:41:01179 return sys.stdin.buffer.read()
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18180
Sybren A. Stüvelb6cebd52019-08-04 14:41:01181 def write_outfile(self, outdata: bytes, outname: str) -> None:
Sybren A. Stüveld3d10342016-01-22 10:36:06182 """Write the output file"""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18183
184 if outname:
Sybren A. Stüvelad727272012-06-18 14:14:37185 print('Writing output to %s' % outname, file=sys.stderr)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18186 with open(outname, 'wb') as outfile:
187 outfile.write(outdata)
188 else:
Sybren A. Stüvelad727272012-06-18 14:14:37189 print('Writing output to stdout', file=sys.stderr)
Sybren A. Stüvelded036c2019-08-04 13:02:20190 sys.stdout.buffer.write(outdata)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18191
Sybren A. Stüveld3d10342016-01-22 10:36:06192
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18193class EncryptOperation(CryptoOperation):
Sybren A. Stüveld3d10342016-01-22 10:36:06194 """Encrypts a file."""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18195
196 keyname = 'public'
197 description = ('Encrypts a file. The file must be shorter than the key '
Sybren A. Stüvel83a81102016-03-17 14:04:04198 'length in order to be encrypted.')
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18199 operation = 'encrypt'
200 operation_past = 'encrypted'
201 operation_progressive = 'encrypting'
202
Sybren A. Stüvelb6cebd52019-08-04 14:41:01203 def perform_operation(self, indata: bytes, pub_key: rsa.key.AbstractKey,
Andrey Semakinae1a9062019-11-04 13:35:23204 cli_args: Indexable = ()) -> bytes:
Sybren A. Stüveld3d10342016-01-22 10:36:06205 """Encrypts files."""
Sybren A. Stüvelb6cebd52019-08-04 14:41:01206 assert isinstance(pub_key, rsa.key.PublicKey)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18207 return rsa.encrypt(indata, pub_key)
208
Sybren A. Stüveld3d10342016-01-22 10:36:06209
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18210class DecryptOperation(CryptoOperation):
Sybren A. Stüveld3d10342016-01-22 10:36:06211 """Decrypts a file."""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18212
213 keyname = 'private'
214 description = ('Decrypts a file. The original file must be shorter than '
Sybren A. Stüvel83a81102016-03-17 14:04:04215 'the key length in order to have been encrypted.')
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18216 operation = 'decrypt'
217 operation_past = 'decrypted'
218 operation_progressive = 'decrypting'
219 key_class = rsa.PrivateKey
220
Sybren A. Stüvelb6cebd52019-08-04 14:41:01221 def perform_operation(self, indata: bytes, priv_key: rsa.key.AbstractKey,
Andrey Semakinae1a9062019-11-04 13:35:23222 cli_args: Indexable = ()) -> bytes:
Sybren A. Stüveld3d10342016-01-22 10:36:06223 """Decrypts files."""
Sybren A. Stüvelb6cebd52019-08-04 14:41:01224 assert isinstance(priv_key, rsa.key.PrivateKey)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18225 return rsa.decrypt(indata, priv_key)
226
Sybren A. Stüveld3d10342016-01-22 10:36:06227
Sybren A. Stüvel85296722011-07-31 20:54:51228class SignOperation(CryptoOperation):
Sybren A. Stüveld3d10342016-01-22 10:36:06229 """Signs a file."""
Sybren A. Stüvel85296722011-07-31 20:54:51230
231 keyname = 'private'
232 usage = 'usage: %%prog [options] private_key hash_method'
233 description = ('Signs a file, outputs the signature. Choose the hash '
Sybren A. Stüveld3d10342016-01-22 10:36:06234 'method from %s' % ', '.join(HASH_METHODS))
Sybren A. Stüvel85296722011-07-31 20:54:51235 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 '
Sybren A. Stüveld3d10342016-01-22 10:36:06242 'to stdout if this option is not present.')
Sybren A. Stüvel85296722011-07-31 20:54:51243
Sybren A. Stüvelb6cebd52019-08-04 14:41:01244 def perform_operation(self, indata: bytes, priv_key: rsa.key.AbstractKey,
Andrey Semakinae1a9062019-11-04 13:35:23245 cli_args: Indexable) -> bytes:
Sybren A. Stüveld3d10342016-01-22 10:36:06246 """Signs files."""
Sybren A. Stüvelb6cebd52019-08-04 14:41:01247 assert isinstance(priv_key, rsa.key.PrivateKey)
Sybren A. Stüvel85296722011-07-31 20:54:51248
249 hash_method = cli_args[1]
250 if hash_method not in HASH_METHODS:
Sybren A. Stüveld3d10342016-01-22 10:36:06251 raise SystemExit('Invalid hash method, choose one of %s' %
252 ', '.join(HASH_METHODS))
Sybren A. Stüvel85296722011-07-31 20:54:51253
254 return rsa.sign(indata, priv_key, hash_method)
255
Sybren A. Stüveld3d10342016-01-22 10:36:06256
Sybren A. Stüvel85296722011-07-31 20:54:51257class VerifyOperation(CryptoOperation):
Sybren A. Stüveld3d10342016-01-22 10:36:06258 """Verify a signature."""
Sybren A. Stüvel85296722011-07-31 20:54:51259
260 keyname = 'public'
Sybren A. Stüvel71eebb12014-02-22 10:18:25261 usage = 'usage: %%prog [options] public_key signature_file'
Sybren A. Stüvel85296722011-07-31 20:54:51262 description = ('Verifies a signature, exits with status 0 upon success, '
Sybren A. Stüveld3d10342016-01-22 10:36:06263 'prints an error message and exits with status 1 upon error.')
Sybren A. Stüvel85296722011-07-31 20:54:51264 operation = 'verify'
265 operation_past = 'verified'
266 operation_progressive = 'Verifying'
267 key_class = rsa.PublicKey
268 expected_cli_args = 2
269 has_output = False
270
Sybren A. Stüvelb6cebd52019-08-04 14:41:01271 def perform_operation(self, indata: bytes, pub_key: rsa.key.AbstractKey,
Andrey Semakinae1a9062019-11-04 13:35:23272 cli_args: Indexable) -> None:
Sybren A. Stüveld3d10342016-01-22 10:36:06273 """Verifies files."""
Sybren A. Stüvelb6cebd52019-08-04 14:41:01274 assert isinstance(pub_key, rsa.key.PublicKey)
Sybren A. Stüvel85296722011-07-31 20:54:51275
276 signature_file = cli_args[1]
Sybren A. Stüveld3d10342016-01-22 10:36:06277
Sybren A. Stüvel85296722011-07-31 20:54:51278 with open(signature_file, 'rb') as sigfile:
279 signature = sigfile.read()
280
281 try:
282 rsa.verify(indata, signature, pub_key)
Ram Rachum1a5b2d12020-06-19 20:39:00283 except rsa.VerificationError as ex:
284 raise SystemExit('Verification failed.') from ex
Sybren A. Stüvel85296722011-07-31 20:54:51285
Sybren A. Stüvelad727272012-06-18 14:14:37286 print('Verification OK', file=sys.stderr)
Sybren A. Stüvel85296722011-07-31 20:54:51287
288
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18289encrypt = EncryptOperation()
290decrypt = DecryptOperation()
Sybren A. Stüvel85296722011-07-31 20:54:51291sign = SignOperation()
292verify = VerifyOperation()