Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 1 | # 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üvel | 3934ab4 | 2016-02-05 15:01:20 | [diff] [blame] | 7 | # https://www.apache.org/licenses/LICENSE-2.0 |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 8 | # |
| 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üvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 15 | """Commandline scripts. |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 16 | |
Sybren A. Stüvel | cca6be6 | 2011-07-31 19:34:27 | [diff] [blame] | 17 | These scripts are called by the executables defined in setup.py. |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 18 | """ |
Yesudeep Mangalapilly | 03c51e7 | 2011-08-16 09:00:48 | [diff] [blame] | 19 | |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 20 | import abc |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 21 | import sys |
Sybren A. Stüvel | 6760eb7 | 2019-08-04 13:47:11 | [diff] [blame] | 22 | import typing |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 23 | import optparse |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 24 | |
| 25 | import rsa |
Sybren A. Stüvel | 6760eb7 | 2019-08-04 13:47:11 | [diff] [blame] | 26 | import rsa.key |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 27 | import rsa.pkcs1 |
| 28 | |
| 29 | HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys()) |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 30 | Indexable = typing.Union[typing.Tuple, typing.List[str]] |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 31 | |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 32 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 33 | def keygen() -> None: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 34 | """Key generator.""" |
Sybren A. Stüvel | cca6be6 | 2011-07-31 19:34:27 | [diff] [blame] | 35 | |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 36 | # Parse the CLI options |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 37 | parser = optparse.OptionParser(usage='usage: %prog [options] keysize', |
Sybren A. Stüvel | f2c3b4f | 2019-08-04 16:05:23 | [diff] [blame] | 38 | description='Generates a new RSA keypair of "keysize" bits.') |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 39 | |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 40 | parser.add_option('--pubout', type='string', |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 41 | 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üvel | 58fe946 | 2011-08-03 11:56:32 | [diff] [blame] | 45 | parser.add_option('-o', '--out', type='string', |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 46 | help='Output filename for the private key. The key is ' |
| 47 | 'written to stdout if this option is not present.') |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 48 | |
| 49 | parser.add_option('--form', |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 50 | help='key format of the private and public keys - default PEM', |
| 51 | choices=('PEM', 'DER'), default='PEM') |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 52 | |
| 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üvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 58 | |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 59 | try: |
| 60 | keysize = int(cli_args[0]) |
Ram Rachum | 1a5b2d1 | 2020-06-19 20:39:00 | [diff] [blame^] | 61 | except ValueError as ex: |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 62 | parser.print_help() |
Sybren A. Stüvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 63 | print('Not a valid number: %s' % cli_args[0], file=sys.stderr) |
Ram Rachum | 1a5b2d1 | 2020-06-19 20:39:00 | [diff] [blame^] | 64 | raise SystemExit(1) from ex |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 65 | |
Sybren A. Stüvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 66 | print('Generating %i-bit key' % keysize, file=sys.stderr) |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 67 | (pub_key, priv_key) = rsa.newkeys(keysize) |
| 68 | |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 69 | # Save public key |
| 70 | if cli.pubout: |
Sybren A. Stüvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 71 | print('Writing public key to %s' % cli.pubout, file=sys.stderr) |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 72 | data = pub_key.save_pkcs1(format=cli.form) |
Sybren A. Stüvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 73 | with open(cli.pubout, 'wb') as outfile: |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 74 | outfile.write(data) |
| 75 | |
| 76 | # Save private key |
| 77 | data = priv_key.save_pkcs1(format=cli.form) |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 78 | |
Sybren A. Stüvel | fc9c786 | 2011-08-03 11:31:47 | [diff] [blame] | 79 | if cli.out: |
Sybren A. Stüvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 80 | print('Writing private key to %s' % cli.out, file=sys.stderr) |
| 81 | with open(cli.out, 'wb') as outfile: |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 82 | outfile.write(data) |
| 83 | else: |
Sybren A. Stüvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 84 | print('Writing private key to stdout', file=sys.stderr) |
Sybren A. Stüvel | ded036c | 2019-08-04 13:02:20 | [diff] [blame] | 85 | sys.stdout.buffer.write(data) |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 86 | |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 87 | |
Sybren A. Stüvel | 6760eb7 | 2019-08-04 13:47:11 | [diff] [blame] | 88 | class CryptoOperation(metaclass=abc.ABCMeta): |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 89 | """CLI callable that operates with input, output, and a key.""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 90 | |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 91 | keyname = 'public' # or 'private' |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 92 | usage = 'usage: %%prog [options] %(keyname)s_key' |
Sybren A. Stüvel | 6760eb7 | 2019-08-04 13:47:11 | [diff] [blame] | 93 | description = '' |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 94 | 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üvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 98 | 'not specified.' |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 99 | output_help = 'Name of the file to write the %(operation_past)s file ' \ |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 100 | 'to. Written to stdout if this option is not present.' |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 101 | expected_cli_args = 1 |
| 102 | has_output = True |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 103 | |
Sybren A. Stüvel | 6760eb7 | 2019-08-04 13:47:11 | [diff] [blame] | 104 | key_class = rsa.PublicKey # type: typing.Type[rsa.key.AbstractKey] |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 105 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 106 | def __init__(self) -> None: |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 107 | 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üvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 112 | def perform_operation(self, indata: bytes, key: rsa.key.AbstractKey, |
Andrey Semakin | ae1a906 | 2019-11-04 13:35:23 | [diff] [blame] | 113 | cli_args: Indexable) -> typing.Any: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 114 | """Performs the program's operation. |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 115 | |
| 116 | Implement in a subclass. |
| 117 | |
| 118 | :returns: the data to write to the output. |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 119 | """ |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 120 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 121 | def __call__(self) -> None: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 122 | """Runs the program.""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 123 | |
| 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üvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 130 | print(self.operation_progressive.title(), file=sys.stderr) |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 131 | outdata = self.perform_operation(indata, key, cli_args) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 132 | |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 133 | if self.has_output: |
| 134 | self.write_outfile(outdata, cli.output) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 135 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 136 | def parse_cli(self) -> typing.Tuple[optparse.Values, typing.List[str]]: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 137 | """Parse the CLI options |
| 138 | |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 139 | :returns: (cli_opts, cli_args) |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 140 | """ |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 141 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 142 | parser = optparse.OptionParser(usage=self.usage, description=self.description) |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 143 | |
Sybren A. Stüvel | 58fe946 | 2011-08-03 11:56:32 | [diff] [blame] | 144 | parser.add_option('-i', '--input', type='string', help=self.input_help) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 145 | |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 146 | if self.has_output: |
Sybren A. Stüvel | 58fe946 | 2011-08-03 11:56:32 | [diff] [blame] | 147 | parser.add_option('-o', '--output', type='string', help=self.output_help) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 148 | |
| 149 | parser.add_option('--keyform', |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 150 | help='Key format of the %s key - default PEM' % self.keyname, |
| 151 | choices=('PEM', 'DER'), default='PEM') |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 152 | |
| 153 | (cli, cli_args) = parser.parse_args(sys.argv[1:]) |
| 154 | |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 155 | if len(cli_args) != self.expected_cli_args: |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 156 | parser.print_help() |
| 157 | raise SystemExit(1) |
| 158 | |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 159 | return cli, cli_args |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 160 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 161 | def read_key(self, filename: str, keyform: str) -> rsa.key.AbstractKey: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 162 | """Reads a public or private key.""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 163 | |
Sybren A. Stüvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 164 | print('Reading %s key from %s' % (self.keyname, filename), file=sys.stderr) |
| 165 | with open(filename, 'rb') as keyfile: |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 166 | keydata = keyfile.read() |
| 167 | |
| 168 | return self.key_class.load_pkcs1(keydata, keyform) |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 169 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 170 | def read_infile(self, inname: str) -> bytes: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 171 | """Read the input file""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 172 | |
| 173 | if inname: |
Sybren A. Stüvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 174 | print('Reading input from %s' % inname, file=sys.stderr) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 175 | with open(inname, 'rb') as infile: |
| 176 | return infile.read() |
| 177 | |
Sybren A. Stüvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 178 | print('Reading input from stdin', file=sys.stderr) |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 179 | return sys.stdin.buffer.read() |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 180 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 181 | def write_outfile(self, outdata: bytes, outname: str) -> None: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 182 | """Write the output file""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 183 | |
| 184 | if outname: |
Sybren A. Stüvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 185 | print('Writing output to %s' % outname, file=sys.stderr) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 186 | with open(outname, 'wb') as outfile: |
| 187 | outfile.write(outdata) |
| 188 | else: |
Sybren A. Stüvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 189 | print('Writing output to stdout', file=sys.stderr) |
Sybren A. Stüvel | ded036c | 2019-08-04 13:02:20 | [diff] [blame] | 190 | sys.stdout.buffer.write(outdata) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 191 | |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 192 | |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 193 | class EncryptOperation(CryptoOperation): |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 194 | """Encrypts a file.""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 195 | |
| 196 | keyname = 'public' |
| 197 | description = ('Encrypts a file. The file must be shorter than the key ' |
Sybren A. Stüvel | 83a8110 | 2016-03-17 14:04:04 | [diff] [blame] | 198 | 'length in order to be encrypted.') |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 199 | operation = 'encrypt' |
| 200 | operation_past = 'encrypted' |
| 201 | operation_progressive = 'encrypting' |
| 202 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 203 | def perform_operation(self, indata: bytes, pub_key: rsa.key.AbstractKey, |
Andrey Semakin | ae1a906 | 2019-11-04 13:35:23 | [diff] [blame] | 204 | cli_args: Indexable = ()) -> bytes: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 205 | """Encrypts files.""" |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 206 | assert isinstance(pub_key, rsa.key.PublicKey) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 207 | return rsa.encrypt(indata, pub_key) |
| 208 | |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 209 | |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 210 | class DecryptOperation(CryptoOperation): |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 211 | """Decrypts a file.""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 212 | |
| 213 | keyname = 'private' |
| 214 | description = ('Decrypts a file. The original file must be shorter than ' |
Sybren A. Stüvel | 83a8110 | 2016-03-17 14:04:04 | [diff] [blame] | 215 | 'the key length in order to have been encrypted.') |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 216 | operation = 'decrypt' |
| 217 | operation_past = 'decrypted' |
| 218 | operation_progressive = 'decrypting' |
| 219 | key_class = rsa.PrivateKey |
| 220 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 221 | def perform_operation(self, indata: bytes, priv_key: rsa.key.AbstractKey, |
Andrey Semakin | ae1a906 | 2019-11-04 13:35:23 | [diff] [blame] | 222 | cli_args: Indexable = ()) -> bytes: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 223 | """Decrypts files.""" |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 224 | assert isinstance(priv_key, rsa.key.PrivateKey) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 225 | return rsa.decrypt(indata, priv_key) |
| 226 | |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 227 | |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 228 | class SignOperation(CryptoOperation): |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 229 | """Signs a file.""" |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 230 | |
| 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üvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 234 | 'method from %s' % ', '.join(HASH_METHODS)) |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 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 ' |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 242 | 'to stdout if this option is not present.') |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 243 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 244 | def perform_operation(self, indata: bytes, priv_key: rsa.key.AbstractKey, |
Andrey Semakin | ae1a906 | 2019-11-04 13:35:23 | [diff] [blame] | 245 | cli_args: Indexable) -> bytes: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 246 | """Signs files.""" |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 247 | assert isinstance(priv_key, rsa.key.PrivateKey) |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 248 | |
| 249 | hash_method = cli_args[1] |
| 250 | if hash_method not in HASH_METHODS: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 251 | raise SystemExit('Invalid hash method, choose one of %s' % |
| 252 | ', '.join(HASH_METHODS)) |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 253 | |
| 254 | return rsa.sign(indata, priv_key, hash_method) |
| 255 | |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 256 | |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 257 | class VerifyOperation(CryptoOperation): |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 258 | """Verify a signature.""" |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 259 | |
| 260 | keyname = 'public' |
Sybren A. Stüvel | 71eebb1 | 2014-02-22 10:18:25 | [diff] [blame] | 261 | usage = 'usage: %%prog [options] public_key signature_file' |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 262 | description = ('Verifies a signature, exits with status 0 upon success, ' |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 263 | 'prints an error message and exits with status 1 upon error.') |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 264 | 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üvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 271 | def perform_operation(self, indata: bytes, pub_key: rsa.key.AbstractKey, |
Andrey Semakin | ae1a906 | 2019-11-04 13:35:23 | [diff] [blame] | 272 | cli_args: Indexable) -> None: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 273 | """Verifies files.""" |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 274 | assert isinstance(pub_key, rsa.key.PublicKey) |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 275 | |
| 276 | signature_file = cli_args[1] |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 277 | |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 278 | with open(signature_file, 'rb') as sigfile: |
| 279 | signature = sigfile.read() |
| 280 | |
| 281 | try: |
| 282 | rsa.verify(indata, signature, pub_key) |
Ram Rachum | 1a5b2d1 | 2020-06-19 20:39:00 | [diff] [blame^] | 283 | except rsa.VerificationError as ex: |
| 284 | raise SystemExit('Verification failed.') from ex |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 285 | |
Sybren A. Stüvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 286 | print('Verification OK', file=sys.stderr) |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 287 | |
| 288 | |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 289 | encrypt = EncryptOperation() |
| 290 | decrypt = DecryptOperation() |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 291 | sign = SignOperation() |
| 292 | verify = VerifyOperation() |