blob: 4db3f0b5e03dcfb6c138a90246b4bcc19e7ea727 [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üvel35e962d2021-03-29 21:17:5537 parser = optparse.OptionParser(
38 usage="usage: %prog [options] keysize",
Kian-Meng, Ang3e9b3382021-10-23 02:15:2139 description='Generates a new RSA key pair of "keysize" bits.',
Sybren A. Stüvel35e962d2021-03-29 21:17:5540 )
Sybren A. Stüveld3d10342016-01-22 10:36:0641
Sybren A. Stüvel35e962d2021-03-29 21:17:5542 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üveld3d10342016-01-22 10:36:0649
Sybren A. Stüvel35e962d2021-03-29 21:17:5550 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üvel64c5f922011-07-31 19:32:0757
Sybren A. Stüvel35e962d2021-03-29 21:17:5558 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üvel64c5f922011-07-31 19:32:0764
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üveld3d10342016-01-22 10:36:0670
Sybren A. Stüvel64c5f922011-07-31 19:32:0771 try:
72 keysize = int(cli_args[0])
Ram Rachum1a5b2d12020-06-19 20:39:0073 except ValueError as ex:
Sybren A. Stüvel64c5f922011-07-31 19:32:0774 parser.print_help()
Sybren A. Stüvel35e962d2021-03-29 21:17:5575 print("Not a valid number: %s" % cli_args[0], file=sys.stderr)
Ram Rachum1a5b2d12020-06-19 20:39:0076 raise SystemExit(1) from ex
Sybren A. Stüvel64c5f922011-07-31 19:32:0777
Sybren A. Stüvel35e962d2021-03-29 21:17:5578 print("Generating %i-bit key" % keysize, file=sys.stderr)
Sybren A. Stüvel64c5f922011-07-31 19:32:0779 (pub_key, priv_key) = rsa.newkeys(keysize)
80
Sybren A. Stüvel64c5f922011-07-31 19:32:0781 # Save public key
82 if cli.pubout:
Sybren A. Stüvel35e962d2021-03-29 21:17:5583 print("Writing public key to %s" % cli.pubout, file=sys.stderr)
Sybren A. Stüvel64c5f922011-07-31 19:32:0784 data = pub_key.save_pkcs1(format=cli.form)
Sybren A. Stüvel35e962d2021-03-29 21:17:5585 with open(cli.pubout, "wb") as outfile:
Sybren A. Stüvel64c5f922011-07-31 19:32:0786 outfile.write(data)
87
88 # Save private key
89 data = priv_key.save_pkcs1(format=cli.form)
Sybren A. Stüveld3d10342016-01-22 10:36:0690
Sybren A. Stüvelfc9c7862011-08-03 11:31:4791 if cli.out:
Sybren A. Stüvel35e962d2021-03-29 21:17:5592 print("Writing private key to %s" % cli.out, file=sys.stderr)
93 with open(cli.out, "wb") as outfile:
Sybren A. Stüvel64c5f922011-07-31 19:32:0794 outfile.write(data)
95 else:
Sybren A. Stüvel35e962d2021-03-29 21:17:5596 print("Writing private key to stdout", file=sys.stderr)
Sybren A. Stüvelded036c2019-08-04 13:02:2097 sys.stdout.buffer.write(data)
Sybren A. Stüvel64c5f922011-07-31 19:32:0798
Sybren A. Stüvel1d15b0b2011-07-31 20:30:1899
Sybren A. Stüvel6760eb72019-08-04 13:47:11100class CryptoOperation(metaclass=abc.ABCMeta):
Sybren A. Stüveld3d10342016-01-22 10:36:06101 """CLI callable that operates with input, output, and a key."""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18102
Sybren A. Stüvel35e962d2021-03-29 21:17:55103 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üvel85296722011-07-31 20:54:51114 expected_cli_args = 1
115 has_output = True
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18116
Sybren A. Stüvel6760eb72019-08-04 13:47:11117 key_class = rsa.PublicKey # type: typing.Type[rsa.key.AbstractKey]
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18118
Sybren A. Stüvelb6cebd52019-08-04 14:41:01119 def __init__(self) -> None:
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18120 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üvel35e962d2021-03-29 21:17:55125 def perform_operation(
126 self, indata: bytes, key: rsa.key.AbstractKey, cli_args: Indexable
127 ) -> typing.Any:
Sybren A. Stüveld3d10342016-01-22 10:36:06128 """Performs the program's operation.
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18129
130 Implement in a subclass.
131
132 :returns: the data to write to the output.
Sybren A. Stüveld3d10342016-01-22 10:36:06133 """
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18134
Sybren A. Stüvelb6cebd52019-08-04 14:41:01135 def __call__(self) -> None:
Sybren A. Stüveld3d10342016-01-22 10:36:06136 """Runs the program."""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18137
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üvelad727272012-06-18 14:14:37144 print(self.operation_progressive.title(), file=sys.stderr)
Sybren A. Stüvel85296722011-07-31 20:54:51145 outdata = self.perform_operation(indata, key, cli_args)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18146
Sybren A. Stüvel85296722011-07-31 20:54:51147 if self.has_output:
148 self.write_outfile(outdata, cli.output)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18149
Sybren A. Stüvelb6cebd52019-08-04 14:41:01150 def parse_cli(self) -> typing.Tuple[optparse.Values, typing.List[str]]:
Sybren A. Stüveld3d10342016-01-22 10:36:06151 """Parse the CLI options
152
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18153 :returns: (cli_opts, cli_args)
Sybren A. Stüveld3d10342016-01-22 10:36:06154 """
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18155
Sybren A. Stüvelb6cebd52019-08-04 14:41:01156 parser = optparse.OptionParser(usage=self.usage, description=self.description)
Sybren A. Stüveld3d10342016-01-22 10:36:06157
Sybren A. Stüvel35e962d2021-03-29 21:17:55158 parser.add_option("-i", "--input", type="string", help=self.input_help)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18159
Sybren A. Stüvel85296722011-07-31 20:54:51160 if self.has_output:
Sybren A. Stüvel35e962d2021-03-29 21:17:55161 parser.add_option("-o", "--output", type="string", help=self.output_help)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18162
Sybren A. Stüvel35e962d2021-03-29 21:17:55163 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üvel1d15b0b2011-07-31 20:30:18169
170 (cli, cli_args) = parser.parse_args(sys.argv[1:])
171
Sybren A. Stüvel85296722011-07-31 20:54:51172 if len(cli_args) != self.expected_cli_args:
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18173 parser.print_help()
174 raise SystemExit(1)
175
Sybren A. Stüveld3d10342016-01-22 10:36:06176 return cli, cli_args
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18177
Sybren A. Stüvelb6cebd52019-08-04 14:41:01178 def read_key(self, filename: str, keyform: str) -> rsa.key.AbstractKey:
Sybren A. Stüveld3d10342016-01-22 10:36:06179 """Reads a public or private key."""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18180
Sybren A. Stüvel35e962d2021-03-29 21:17:55181 print("Reading %s key from %s" % (self.keyname, filename), file=sys.stderr)
182 with open(filename, "rb") as keyfile:
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18183 keydata = keyfile.read()
184
185 return self.key_class.load_pkcs1(keydata, keyform)
Sybren A. Stüveld3d10342016-01-22 10:36:06186
Sybren A. Stüvelb6cebd52019-08-04 14:41:01187 def read_infile(self, inname: str) -> bytes:
Sybren A. Stüveld3d10342016-01-22 10:36:06188 """Read the input file"""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18189
190 if inname:
Sybren A. Stüvel35e962d2021-03-29 21:17:55191 print("Reading input from %s" % inname, file=sys.stderr)
192 with open(inname, "rb") as infile:
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18193 return infile.read()
194
Sybren A. Stüvel35e962d2021-03-29 21:17:55195 print("Reading input from stdin", file=sys.stderr)
Sybren A. Stüvelb6cebd52019-08-04 14:41:01196 return sys.stdin.buffer.read()
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18197
Sybren A. Stüvelb6cebd52019-08-04 14:41:01198 def write_outfile(self, outdata: bytes, outname: str) -> None:
Sybren A. Stüveld3d10342016-01-22 10:36:06199 """Write the output file"""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18200
201 if outname:
Sybren A. Stüvel35e962d2021-03-29 21:17:55202 print("Writing output to %s" % outname, file=sys.stderr)
203 with open(outname, "wb") as outfile:
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18204 outfile.write(outdata)
205 else:
Sybren A. Stüvel35e962d2021-03-29 21:17:55206 print("Writing output to stdout", file=sys.stderr)
Sybren A. Stüvelded036c2019-08-04 13:02:20207 sys.stdout.buffer.write(outdata)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18208
Sybren A. Stüveld3d10342016-01-22 10:36:06209
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18210class EncryptOperation(CryptoOperation):
Sybren A. Stüveld3d10342016-01-22 10:36:06211 """Encrypts a file."""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18212
Sybren A. Stüvel35e962d2021-03-29 21:17:55213 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üvel1d15b0b2011-07-31 20:30:18220
Sybren A. Stüvel35e962d2021-03-29 21:17:55221 def perform_operation(
222 self, indata: bytes, pub_key: rsa.key.AbstractKey, cli_args: Indexable = ()
223 ) -> bytes:
Sybren A. Stüveld3d10342016-01-22 10:36:06224 """Encrypts files."""
Sybren A. Stüvelb6cebd52019-08-04 14:41:01225 assert isinstance(pub_key, rsa.key.PublicKey)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18226 return rsa.encrypt(indata, pub_key)
227
Sybren A. Stüveld3d10342016-01-22 10:36:06228
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18229class DecryptOperation(CryptoOperation):
Sybren A. Stüveld3d10342016-01-22 10:36:06230 """Decrypts a file."""
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18231
Sybren A. Stüvel35e962d2021-03-29 21:17:55232 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üvel1d15b0b2011-07-31 20:30:18240 key_class = rsa.PrivateKey
241
Sybren A. Stüvel35e962d2021-03-29 21:17:55242 def perform_operation(
243 self, indata: bytes, priv_key: rsa.key.AbstractKey, cli_args: Indexable = ()
244 ) -> bytes:
Sybren A. Stüveld3d10342016-01-22 10:36:06245 """Decrypts files."""
Sybren A. Stüvelb6cebd52019-08-04 14:41:01246 assert isinstance(priv_key, rsa.key.PrivateKey)
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18247 return rsa.decrypt(indata, priv_key)
248
Sybren A. Stüveld3d10342016-01-22 10:36:06249
Sybren A. Stüvel85296722011-07-31 20:54:51250class SignOperation(CryptoOperation):
Sybren A. Stüveld3d10342016-01-22 10:36:06251 """Signs a file."""
Sybren A. Stüvel85296722011-07-31 20:54:51252
Sybren A. Stüvel35e962d2021-03-29 21:17:55253 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üvel85296722011-07-31 20:54:51262 key_class = rsa.PrivateKey
263 expected_cli_args = 2
264
Sybren A. Stüvel35e962d2021-03-29 21:17:55265 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üvel85296722011-07-31 20:54:51269
Sybren A. Stüvel35e962d2021-03-29 21:17:55270 def perform_operation(
271 self, indata: bytes, priv_key: rsa.key.AbstractKey, cli_args: Indexable
272 ) -> bytes:
Sybren A. Stüveld3d10342016-01-22 10:36:06273 """Signs files."""
Sybren A. Stüvelb6cebd52019-08-04 14:41:01274 assert isinstance(priv_key, rsa.key.PrivateKey)
Sybren A. Stüvel85296722011-07-31 20:54:51275
276 hash_method = cli_args[1]
277 if hash_method not in HASH_METHODS:
Sybren A. Stüvel35e962d2021-03-29 21:17:55278 raise SystemExit("Invalid hash method, choose one of %s" % ", ".join(HASH_METHODS))
Sybren A. Stüvel85296722011-07-31 20:54:51279
280 return rsa.sign(indata, priv_key, hash_method)
281
Sybren A. Stüveld3d10342016-01-22 10:36:06282
Sybren A. Stüvel85296722011-07-31 20:54:51283class VerifyOperation(CryptoOperation):
Sybren A. Stüveld3d10342016-01-22 10:36:06284 """Verify a signature."""
Sybren A. Stüvel85296722011-07-31 20:54:51285
Sybren A. Stüvel35e962d2021-03-29 21:17:55286 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üvel85296722011-07-31 20:54:51295 key_class = rsa.PublicKey
296 expected_cli_args = 2
297 has_output = False
298
Sybren A. Stüvel35e962d2021-03-29 21:17:55299 def perform_operation(
300 self, indata: bytes, pub_key: rsa.key.AbstractKey, cli_args: Indexable
301 ) -> None:
Sybren A. Stüveld3d10342016-01-22 10:36:06302 """Verifies files."""
Sybren A. Stüvelb6cebd52019-08-04 14:41:01303 assert isinstance(pub_key, rsa.key.PublicKey)
Sybren A. Stüvel85296722011-07-31 20:54:51304
305 signature_file = cli_args[1]
Sybren A. Stüveld3d10342016-01-22 10:36:06306
Sybren A. Stüvel35e962d2021-03-29 21:17:55307 with open(signature_file, "rb") as sigfile:
Sybren A. Stüvel85296722011-07-31 20:54:51308 signature = sigfile.read()
309
310 try:
311 rsa.verify(indata, signature, pub_key)
Ram Rachum1a5b2d12020-06-19 20:39:00312 except rsa.VerificationError as ex:
Sybren A. Stüvel35e962d2021-03-29 21:17:55313 raise SystemExit("Verification failed.") from ex
Sybren A. Stüvel85296722011-07-31 20:54:51314
Sybren A. Stüvel35e962d2021-03-29 21:17:55315 print("Verification OK", file=sys.stderr)
Sybren A. Stüvel85296722011-07-31 20:54:51316
317
Sybren A. Stüvel1d15b0b2011-07-31 20:30:18318encrypt = EncryptOperation()
319decrypt = DecryptOperation()
Sybren A. Stüvel85296722011-07-31 20:54:51320sign = SignOperation()
321verify = VerifyOperation()