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 | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 37 | parser = optparse.OptionParser( |
| 38 | usage="usage: %prog [options] keysize", |
Kian-Meng, Ang | 3e9b338 | 2021-10-23 02:15:21 | [diff] [blame] | 39 | description='Generates a new RSA key pair of "keysize" bits.', |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 40 | ) |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 41 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 42 | parser.add_option( |
| 43 | "--pubout", |
| 44 | type="string", |
| 45 | help="Output filename for the public key. The public key is " |
| 46 | "not saved if this option is not present. You can use " |
| 47 | "pyrsa-priv2pub to create the public key file later.", |
| 48 | ) |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 49 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 50 | parser.add_option( |
| 51 | "-o", |
| 52 | "--out", |
| 53 | type="string", |
| 54 | help="Output filename for the private key. The key is " |
| 55 | "written to stdout if this option is not present.", |
| 56 | ) |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 57 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 58 | parser.add_option( |
| 59 | "--form", |
| 60 | help="key format of the private and public keys - default PEM", |
| 61 | choices=("PEM", "DER"), |
| 62 | default="PEM", |
| 63 | ) |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 64 | |
| 65 | (cli, cli_args) = parser.parse_args(sys.argv[1:]) |
| 66 | |
| 67 | if len(cli_args) != 1: |
| 68 | parser.print_help() |
| 69 | raise SystemExit(1) |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 70 | |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 71 | try: |
| 72 | keysize = int(cli_args[0]) |
Ram Rachum | 1a5b2d1 | 2020-06-19 20:39:00 | [diff] [blame] | 73 | except ValueError as ex: |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 74 | parser.print_help() |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 75 | print("Not a valid number: %s" % cli_args[0], file=sys.stderr) |
Ram Rachum | 1a5b2d1 | 2020-06-19 20:39:00 | [diff] [blame] | 76 | raise SystemExit(1) from ex |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 77 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 78 | print("Generating %i-bit key" % keysize, file=sys.stderr) |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 79 | (pub_key, priv_key) = rsa.newkeys(keysize) |
| 80 | |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 81 | # Save public key |
| 82 | if cli.pubout: |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 83 | print("Writing public key to %s" % cli.pubout, file=sys.stderr) |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 84 | data = pub_key.save_pkcs1(format=cli.form) |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 85 | with open(cli.pubout, "wb") as outfile: |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 86 | outfile.write(data) |
| 87 | |
| 88 | # Save private key |
| 89 | data = priv_key.save_pkcs1(format=cli.form) |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 90 | |
Sybren A. Stüvel | fc9c786 | 2011-08-03 11:31:47 | [diff] [blame] | 91 | if cli.out: |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 92 | print("Writing private key to %s" % cli.out, file=sys.stderr) |
| 93 | with open(cli.out, "wb") as outfile: |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 94 | outfile.write(data) |
| 95 | else: |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 96 | print("Writing private key to stdout", file=sys.stderr) |
Sybren A. Stüvel | ded036c | 2019-08-04 13:02:20 | [diff] [blame] | 97 | sys.stdout.buffer.write(data) |
Sybren A. Stüvel | 64c5f92 | 2011-07-31 19:32:07 | [diff] [blame] | 98 | |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 99 | |
Sybren A. Stüvel | 6760eb7 | 2019-08-04 13:47:11 | [diff] [blame] | 100 | class CryptoOperation(metaclass=abc.ABCMeta): |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 101 | """CLI callable that operates with input, output, and a key.""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 102 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 103 | keyname = "public" # or 'private' |
| 104 | usage = "usage: %%prog [options] %(keyname)s_key" |
| 105 | description = "" |
| 106 | operation = "decrypt" |
| 107 | operation_past = "decrypted" |
| 108 | operation_progressive = "decrypting" |
| 109 | input_help = "Name of the file to %(operation)s. Reads from stdin if " "not specified." |
| 110 | output_help = ( |
| 111 | "Name of the file to write the %(operation_past)s file " |
| 112 | "to. Written to stdout if this option is not present." |
| 113 | ) |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 114 | expected_cli_args = 1 |
| 115 | has_output = True |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 116 | |
Sybren A. Stüvel | 6760eb7 | 2019-08-04 13:47:11 | [diff] [blame] | 117 | key_class = rsa.PublicKey # type: typing.Type[rsa.key.AbstractKey] |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 118 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 119 | def __init__(self) -> None: |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 120 | self.usage = self.usage % self.__class__.__dict__ |
| 121 | self.input_help = self.input_help % self.__class__.__dict__ |
| 122 | self.output_help = self.output_help % self.__class__.__dict__ |
| 123 | |
| 124 | @abc.abstractmethod |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 125 | def perform_operation( |
| 126 | self, indata: bytes, key: rsa.key.AbstractKey, cli_args: Indexable |
| 127 | ) -> typing.Any: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 128 | """Performs the program's operation. |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 129 | |
| 130 | Implement in a subclass. |
| 131 | |
| 132 | :returns: the data to write to the output. |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 133 | """ |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 134 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 135 | def __call__(self) -> None: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 136 | """Runs the program.""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 137 | |
| 138 | (cli, cli_args) = self.parse_cli() |
| 139 | |
| 140 | key = self.read_key(cli_args[0], cli.keyform) |
| 141 | |
| 142 | indata = self.read_infile(cli.input) |
| 143 | |
Sybren A. Stüvel | ad72727 | 2012-06-18 14:14:37 | [diff] [blame] | 144 | print(self.operation_progressive.title(), file=sys.stderr) |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 145 | outdata = self.perform_operation(indata, key, cli_args) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 146 | |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 147 | if self.has_output: |
| 148 | self.write_outfile(outdata, cli.output) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 149 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 150 | def parse_cli(self) -> typing.Tuple[optparse.Values, typing.List[str]]: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 151 | """Parse the CLI options |
| 152 | |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 153 | :returns: (cli_opts, cli_args) |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 154 | """ |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 155 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 156 | parser = optparse.OptionParser(usage=self.usage, description=self.description) |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 157 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 158 | parser.add_option("-i", "--input", type="string", help=self.input_help) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 159 | |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 160 | if self.has_output: |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 161 | parser.add_option("-o", "--output", type="string", help=self.output_help) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 162 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 163 | parser.add_option( |
| 164 | "--keyform", |
| 165 | help="Key format of the %s key - default PEM" % self.keyname, |
| 166 | choices=("PEM", "DER"), |
| 167 | default="PEM", |
| 168 | ) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 169 | |
| 170 | (cli, cli_args) = parser.parse_args(sys.argv[1:]) |
| 171 | |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 172 | if len(cli_args) != self.expected_cli_args: |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 173 | parser.print_help() |
| 174 | raise SystemExit(1) |
| 175 | |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 176 | return cli, cli_args |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 177 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 178 | def read_key(self, filename: str, keyform: str) -> rsa.key.AbstractKey: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 179 | """Reads a public or private key.""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 180 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 181 | print("Reading %s key from %s" % (self.keyname, filename), file=sys.stderr) |
| 182 | with open(filename, "rb") as keyfile: |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 183 | keydata = keyfile.read() |
| 184 | |
| 185 | return self.key_class.load_pkcs1(keydata, keyform) |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 186 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 187 | def read_infile(self, inname: str) -> bytes: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 188 | """Read the input file""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 189 | |
| 190 | if inname: |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 191 | print("Reading input from %s" % inname, file=sys.stderr) |
| 192 | with open(inname, "rb") as infile: |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 193 | return infile.read() |
| 194 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 195 | print("Reading input from stdin", file=sys.stderr) |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 196 | return sys.stdin.buffer.read() |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 197 | |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 198 | def write_outfile(self, outdata: bytes, outname: str) -> None: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 199 | """Write the output file""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 200 | |
| 201 | if outname: |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 202 | print("Writing output to %s" % outname, file=sys.stderr) |
| 203 | with open(outname, "wb") as outfile: |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 204 | outfile.write(outdata) |
| 205 | else: |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 206 | print("Writing output to stdout", file=sys.stderr) |
Sybren A. Stüvel | ded036c | 2019-08-04 13:02:20 | [diff] [blame] | 207 | sys.stdout.buffer.write(outdata) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 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 EncryptOperation(CryptoOperation): |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 211 | """Encrypts a file.""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 212 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 213 | keyname = "public" |
| 214 | description = ( |
| 215 | "Encrypts a file. The file must be shorter than the key " "length in order to be encrypted." |
| 216 | ) |
| 217 | operation = "encrypt" |
| 218 | operation_past = "encrypted" |
| 219 | operation_progressive = "encrypting" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 220 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 221 | def perform_operation( |
| 222 | self, indata: bytes, pub_key: rsa.key.AbstractKey, cli_args: Indexable = () |
| 223 | ) -> bytes: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 224 | """Encrypts files.""" |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 225 | assert isinstance(pub_key, rsa.key.PublicKey) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 226 | return rsa.encrypt(indata, pub_key) |
| 227 | |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 228 | |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 229 | class DecryptOperation(CryptoOperation): |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 230 | """Decrypts a file.""" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 231 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 232 | keyname = "private" |
| 233 | description = ( |
| 234 | "Decrypts a file. The original file must be shorter than " |
| 235 | "the key length in order to have been encrypted." |
| 236 | ) |
| 237 | operation = "decrypt" |
| 238 | operation_past = "decrypted" |
| 239 | operation_progressive = "decrypting" |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 240 | key_class = rsa.PrivateKey |
| 241 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 242 | def perform_operation( |
| 243 | self, indata: bytes, priv_key: rsa.key.AbstractKey, cli_args: Indexable = () |
| 244 | ) -> bytes: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 245 | """Decrypts files.""" |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 246 | assert isinstance(priv_key, rsa.key.PrivateKey) |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 247 | return rsa.decrypt(indata, priv_key) |
| 248 | |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 249 | |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 250 | class SignOperation(CryptoOperation): |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 251 | """Signs a file.""" |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 252 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 253 | keyname = "private" |
| 254 | usage = "usage: %%prog [options] private_key hash_method" |
| 255 | description = ( |
| 256 | "Signs a file, outputs the signature. Choose the hash " |
| 257 | "method from %s" % ", ".join(HASH_METHODS) |
| 258 | ) |
| 259 | operation = "sign" |
| 260 | operation_past = "signature" |
| 261 | operation_progressive = "Signing" |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 262 | key_class = rsa.PrivateKey |
| 263 | expected_cli_args = 2 |
| 264 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 265 | output_help = ( |
| 266 | "Name of the file to write the signature to. Written " |
| 267 | "to stdout if this option is not present." |
| 268 | ) |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 269 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 270 | def perform_operation( |
| 271 | self, indata: bytes, priv_key: rsa.key.AbstractKey, cli_args: Indexable |
| 272 | ) -> bytes: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 273 | """Signs files.""" |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 274 | assert isinstance(priv_key, rsa.key.PrivateKey) |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 275 | |
| 276 | hash_method = cli_args[1] |
| 277 | if hash_method not in HASH_METHODS: |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 278 | raise SystemExit("Invalid hash method, choose one of %s" % ", ".join(HASH_METHODS)) |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 279 | |
| 280 | return rsa.sign(indata, priv_key, hash_method) |
| 281 | |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 282 | |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 283 | class VerifyOperation(CryptoOperation): |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 284 | """Verify a signature.""" |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 285 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 286 | keyname = "public" |
| 287 | usage = "usage: %%prog [options] public_key signature_file" |
| 288 | description = ( |
| 289 | "Verifies a signature, exits with status 0 upon success, " |
| 290 | "prints an error message and exits with status 1 upon error." |
| 291 | ) |
| 292 | operation = "verify" |
| 293 | operation_past = "verified" |
| 294 | operation_progressive = "Verifying" |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 295 | key_class = rsa.PublicKey |
| 296 | expected_cli_args = 2 |
| 297 | has_output = False |
| 298 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 299 | def perform_operation( |
| 300 | self, indata: bytes, pub_key: rsa.key.AbstractKey, cli_args: Indexable |
| 301 | ) -> None: |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 302 | """Verifies files.""" |
Sybren A. Stüvel | b6cebd5 | 2019-08-04 14:41:01 | [diff] [blame] | 303 | assert isinstance(pub_key, rsa.key.PublicKey) |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 304 | |
| 305 | signature_file = cli_args[1] |
Sybren A. Stüvel | d3d1034 | 2016-01-22 10:36:06 | [diff] [blame] | 306 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 307 | with open(signature_file, "rb") as sigfile: |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 308 | signature = sigfile.read() |
| 309 | |
| 310 | try: |
| 311 | rsa.verify(indata, signature, pub_key) |
Ram Rachum | 1a5b2d1 | 2020-06-19 20:39:00 | [diff] [blame] | 312 | except rsa.VerificationError as ex: |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 313 | raise SystemExit("Verification failed.") from ex |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 314 | |
Sybren A. Stüvel | 35e962d | 2021-03-29 21:17:55 | [diff] [blame] | 315 | print("Verification OK", file=sys.stderr) |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 316 | |
| 317 | |
Sybren A. Stüvel | 1d15b0b | 2011-07-31 20:30:18 | [diff] [blame] | 318 | encrypt = EncryptOperation() |
| 319 | decrypt = DecryptOperation() |
Sybren A. Stüvel | 8529672 | 2011-07-31 20:54:51 | [diff] [blame] | 320 | sign = SignOperation() |
| 321 | verify = VerifyOperation() |