1#!/usr/bin/env python 2 3""" 4<Program Name> 5 interface.py 6 7<Author> 8 Vladimir Diaz <vladimir.v.diaz@gmail.com> 9 10<Started> 11 January 5, 2017. 12 13<Copyright> 14 See LICENSE for licensing information. 15 16<Purpose> 17 Provide an interface to the cryptography functions available in 18 securesystemslib. The interface can be used with the Python interpreter in 19 interactive mode, or imported directly into a Python module. See 20 'securesystemslib/README' for the complete guide to using 'interface.py'. 21""" 22 23# Help with Python 3 compatibility, where the print statement is a function, an 24# implicit relative import is invalid, and the '/' operator performs true 25# division. Example: print 'hello world' raises a 'SyntaxError' exception. 26from __future__ import print_function 27from __future__ import absolute_import 28from __future__ import division 29from __future__ import unicode_literals 30 31import os 32import errno 33import sys 34import time 35import datetime 36import getpass 37import logging 38import tempfile 39import shutil 40import json 41import gzip 42import random 43 44import securesystemslib.formats 45import securesystemslib.settings 46import securesystemslib.util 47import securesystemslib.keys 48 49import six 50 51from colorama import Fore 52 53# See 'log.py' to learn how logging is handled in securesystemslib. 54logger = logging.getLogger('securesystemslib_interface') 55 56 57# Recommended RSA key sizes: 58# https://en.wikipedia.org/wiki/Key_size#Asymmetric_algorithm_key_lengths 59# Based on the above, RSA keys of size 3072 bits are expected to provide 60# security through 2031 and beyond. 61DEFAULT_RSA_KEY_BITS = 3072 62 63# Supported key types. 64SUPPORTED_KEY_TYPES = ['rsa', 'ed25519'] 65 66 67 68def _prompt(message, result_type=str): 69 """ 70 Non-public function that prompts the user for input by logging 'message', 71 converting the input to 'result_type', and returning the value to the 72 caller. 73 """ 74 75 return result_type(six.moves.input(message)) 76 77 78 79 80 81def get_password(prompt='Password: ', confirm=False): 82 """ 83 <Purpose> 84 Return the password entered by the user. If 'confirm' is True, the user is 85 asked to enter the previously entered password once again. If they match, 86 the password is returned to the caller. 87 88 <Arguments> 89 prompt: 90 The text of the password prompt that is displayed to the user. 91 92 confirm: 93 Boolean indicating whether the user should be prompted for the password 94 a second time. The two entered password must match, otherwise the 95 user is again prompted for a password. 96 97 <Exceptions> 98 None. 99 100 <Side Effects> 101 None. 102 103 <Returns> 104 The password entered by the user. 105 """ 106 107 # Are the arguments the expected type? 108 # If not, raise 'securesystemslib.exceptions.FormatError'. 109 securesystemslib.formats.TEXT_SCHEMA.check_match(prompt) 110 securesystemslib.formats.BOOLEAN_SCHEMA.check_match(confirm) 111 112 while True: 113 # getpass() prompts the user for a password without echoing 114 # the user input. 115 password = getpass.getpass(prompt, sys.stderr) 116 117 if not confirm: 118 return password 119 password2 = getpass.getpass('Confirm: ', sys.stderr) 120 121 if password == password2: 122 return password 123 124 else: 125 print('Mismatch; try again.') 126 127 128 129 130 131def generate_and_write_rsa_keypair(filepath=None, bits=DEFAULT_RSA_KEY_BITS, 132 password=None): 133 """ 134 <Purpose> 135 Generate an RSA key pair. The public portion of the generated RSA key is 136 saved to <'filepath'>.pub, whereas the private key portion is saved to 137 <'filepath'>. If no password is given, the user is prompted for one. If 138 the 'password' is an empty string, the private key is saved unencrypted to 139 <'filepath'>. If the filepath is not given, the KEYID is used as the 140 filename and the keypair saved to the current working directory. 141 142 The best available form of encryption, for a given key's backend, is used 143 with pyca/cryptography. According to their documentation, "it is a curated 144 encryption choice and the algorithm may change over time." 145 146 <Arguments> 147 filepath: 148 The public and private key files are saved to <filepath>.pub and 149 <filepath>, respectively. If the filepath is not given, the public and 150 private keys are saved to the current working directory as <KEYID>.pub 151 and <KEYID>. KEYID is the generated key's KEYID. 152 153 bits: 154 The number of bits of the generated RSA key. 155 156 password: 157 The password to encrypt 'filepath'. If None, the user is prompted for a 158 password. If an empty string is given, the private key is written to 159 disk unencrypted. 160 161 <Exceptions> 162 securesystemslib.exceptions.FormatError, if the arguments are improperly 163 formatted. 164 165 <Side Effects> 166 Writes key files to '<filepath>' and '<filepath>.pub'. 167 168 <Returns> 169 The 'filepath' of the written key. 170 """ 171 172 # Does 'bits' have the correct format? 173 # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. 174 securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(bits) 175 176 # Generate the public and private RSA keys. 177 rsa_key = securesystemslib.keys.generate_rsa_key(bits) 178 public = rsa_key['keyval']['public'] 179 private = rsa_key['keyval']['private'] 180 181 if not filepath: 182 filepath = os.path.join(os.getcwd(), rsa_key['keyid']) 183 184 else: 185 logger.debug('The filepath has been specified. Not using the key\'s' 186 ' KEYID as the default filepath.') 187 188 # Does 'filepath' have the correct format? 189 securesystemslib.formats.PATH_SCHEMA.check_match(filepath) 190 191 # If the caller does not provide a password argument, prompt for one. 192 if password is None: # pragma: no cover 193 194 # It is safe to specify the full path of 'filepath' in the prompt and not 195 # worry about leaking sensitive information about the key's location. 196 # However, care should be taken when including the full path in exceptions 197 # and log files. 198 password = get_password('Enter a password for the encrypted RSA' 199 ' key (' + Fore.RED + filepath + Fore.RESET + '): ', 200 confirm=True) 201 202 else: 203 logger.debug('The password has been specified. Not prompting for one') 204 205 # Does 'password' have the correct format? 206 securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) 207 208 # Encrypt the private key if 'password' is set. 209 if len(password): 210 private = securesystemslib.keys.create_rsa_encrypted_pem(private, password) 211 212 else: 213 logger.debug('An empty password was given. Not encrypting the private key.') 214 215 # If the parent directory of filepath does not exist, 216 # create it (and all its parent directories, if necessary). 217 securesystemslib.util.ensure_parent_dir(filepath) 218 219 # Write the public key (i.e., 'public', which is in PEM format) to 220 # '<filepath>.pub'. (1) Create a temporary file, (2) write the contents of 221 # the public key, and (3) move to final destination. 222 file_object = securesystemslib.util.TempFile() 223 file_object.write(public.encode('utf-8')) 224 # The temporary file is closed after the final move. 225 file_object.move(filepath + '.pub') 226 227 # Write the private key in encrypted PEM format to '<filepath>'. 228 # Unlike the public key file, the private key does not have a file 229 # extension. 230 file_object = securesystemslib.util.TempFile() 231 file_object.write(private.encode('utf-8')) 232 file_object.move(filepath) 233 234 return filepath 235 236 237 238 239def import_rsa_privatekey_from_file(filepath, password=None, 240 scheme='rsassa-pss-sha256', prompt=False): 241 """ 242 <Purpose> 243 Import the PEM file in 'filepath' containing the private key. 244 245 If password is passed use passed password for decryption. 246 If prompt is True use entered password for decryption. 247 If no password is passed and either prompt is False or if the password 248 entered at the prompt is an empty string, omit decryption, treating the 249 key as if it is not encrypted. 250 If password is passed and prompt is True, an error is raised. (See below.) 251 252 The returned key is an object in the 253 'securesystemslib.formats.RSAKEY_SCHEMA' format. 254 255 <Arguments> 256 filepath: 257 <filepath> file, an RSA encrypted PEM file. Unlike the public RSA PEM 258 key file, 'filepath' does not have an extension. 259 260 password: 261 The passphrase to decrypt 'filepath'. 262 263 scheme: 264 The signature scheme used by the imported key. 265 266 prompt: 267 If True the user is prompted for a passphrase to decrypt 'filepath'. 268 Default is False. 269 270 <Exceptions> 271 ValueError, if 'password' is passed and 'prompt' is True. 272 273 ValueError, if 'password' is passed and it is an empty string. 274 275 securesystemslib.exceptions.FormatError, if the arguments are improperly 276 formatted. 277 278 securesystemslib.exceptions.FormatError, if the entered password is 279 improperly formatted. 280 281 IOError, if 'filepath' can't be loaded. 282 283 securesystemslib.exceptions.CryptoError, if a password is available 284 and 'filepath' is not a valid key file encrypted using that password. 285 286 securesystemslib.exceptions.CryptoError, if no password is available 287 and 'filepath' is not a valid non-encrypted key file. 288 289 <Side Effects> 290 The contents of 'filepath' are read, optionally decrypted, and returned. 291 292 <Returns> 293 An RSA key object, conformant to 'securesystemslib.formats.RSAKEY_SCHEMA'. 294 295 """ 296 297 # Does 'filepath' have the correct format? 298 # Ensure the arguments have the appropriate number of objects and object 299 # types, and that all dict keys are properly named. 300 # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. 301 securesystemslib.formats.PATH_SCHEMA.check_match(filepath) 302 303 # Is 'scheme' properly formatted? 304 securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme) 305 306 if password and prompt: 307 raise ValueError("Passing 'password' and 'prompt' True is not allowed.") 308 309 # If 'password' was passed check format and that it is not empty. 310 if password is not None: 311 securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) 312 313 # TODO: PASSWORD_SCHEMA should be securesystemslib.schema.AnyString(min=1) 314 if not len(password): 315 raise ValueError('Password must be 1 or more characters') 316 317 elif prompt: 318 # Password confirmation disabled here, which should ideally happen only 319 # when creating encrypted key files (i.e., improve usability). 320 # It is safe to specify the full path of 'filepath' in the prompt and not 321 # worry about leaking sensitive information about the key's location. 322 # However, care should be taken when including the full path in exceptions 323 # and log files. 324 # NOTE: A user who gets prompted for a password, can only signal that the 325 # key is not encrypted by entering no password in the prompt, as opposed 326 # to a programmer who can call the function with or without a 'password'. 327 # Hence, we treat an empty password here, as if no 'password' was passed. 328 password = get_password('Enter a password for an encrypted RSA' 329 ' file \'' + Fore.RED + filepath + Fore.RESET + '\': ', 330 confirm=False) or None 331 332 if password is not None: 333 # This check will not fail, because a mal-formatted passed password fails 334 # above and an entered password will always be a string (see get_password) 335 # However, we include it in case PASSWORD_SCHEMA or get_password changes. 336 securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) 337 338 else: 339 logger.debug('No password was given. Attempting to import an' 340 ' unencrypted file.') 341 342 # Read the contents of 'filepath' that should be a PEM formatted private key. 343 with open(filepath, 'rb') as file_object: 344 pem_key = file_object.read().decode('utf-8') 345 346 # Convert 'pem_key' to 'securesystemslib.formats.RSAKEY_SCHEMA' format. 347 # Raise 'securesystemslib.exceptions.CryptoError' if 'pem_key' is invalid. 348 # If 'password' is None decryption will be omitted. 349 rsa_key = securesystemslib.keys.import_rsakey_from_private_pem(pem_key, 350 scheme, password) 351 352 return rsa_key 353 354 355 356 357 358def import_rsa_publickey_from_file(filepath): 359 """ 360 <Purpose> 361 Import the RSA key stored in 'filepath'. The key object returned is in the 362 format 'securesystemslib.formats.RSAKEY_SCHEMA'. If the RSA PEM in 363 'filepath' contains a private key, it is discarded. 364 365 <Arguments> 366 filepath: 367 <filepath>.pub file, an RSA PEM file. 368 369 <Exceptions> 370 securesystemslib.exceptions.FormatError, if 'filepath' is improperly 371 formatted. 372 373 securesystemslib.exceptions.Error, if a valid RSA key object cannot be 374 generated. This may be caused by an improperly formatted PEM file. 375 376 <Side Effects> 377 'filepath' is read and its contents extracted. 378 379 <Returns> 380 An RSA key object conformant to 'securesystemslib.formats.RSAKEY_SCHEMA'. 381 """ 382 383 # Does 'filepath' have the correct format? 384 # Ensure the arguments have the appropriate number of objects and object 385 # types, and that all dict keys are properly named. 386 # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. 387 securesystemslib.formats.PATH_SCHEMA.check_match(filepath) 388 389 # Read the contents of the key file that should be in PEM format and contains 390 # the public portion of the RSA key. 391 with open(filepath, 'rb') as file_object: 392 rsa_pubkey_pem = file_object.read().decode('utf-8') 393 394 # Convert 'rsa_pubkey_pem' to 'securesystemslib.formats.RSAKEY_SCHEMA' format. 395 try: 396 rsakey_dict = securesystemslib.keys.import_rsakey_from_public_pem(rsa_pubkey_pem) 397 398 except securesystemslib.exceptions.FormatError as e: 399 raise securesystemslib.exceptions.Error('Cannot import improperly formatted' 400 ' PEM file.' + repr(str(e))) 401 402 return rsakey_dict 403 404 405 406 407 408def generate_and_write_ed25519_keypair(filepath=None, password=None): 409 """ 410 <Purpose> 411 Generate an Ed25519 keypair, where the encrypted key (using 'password' as 412 the passphrase) is saved to <'filepath'>. The public key portion of the 413 generated Ed25519 key is saved to <'filepath'>.pub. If the filepath is not 414 given, the KEYID is used as the filename and the keypair saved to the 415 current working directory. 416 417 The private key is encrypted according to 'cryptography's approach: 418 "Encrypt using the best available encryption for a given key's backend. 419 This is a curated encryption choice and the algorithm may change over 420 time." 421 422 <Arguments> 423 filepath: 424 The public and private key files are saved to <filepath>.pub and 425 <filepath>, respectively. If the filepath is not given, the public and 426 private keys are saved to the current working directory as <KEYID>.pub 427 and <KEYID>. KEYID is the generated key's KEYID. 428 429 password: 430 The password, or passphrase, to encrypt the private portion of the 431 generated Ed25519 key. A symmetric encryption key is derived from 432 'password', so it is not directly used. 433 434 <Exceptions> 435 securesystemslib.exceptions.FormatError, if the arguments are improperly 436 formatted. 437 438 securesystemslib.exceptions.CryptoError, if 'filepath' cannot be encrypted. 439 440 <Side Effects> 441 Writes key files to '<filepath>' and '<filepath>.pub'. 442 443 <Returns> 444 The 'filepath' of the written key. 445 """ 446 447 # Generate a new Ed25519 key object. 448 ed25519_key = securesystemslib.keys.generate_ed25519_key() 449 450 if not filepath: 451 filepath = os.path.join(os.getcwd(), ed25519_key['keyid']) 452 453 else: 454 logger.debug('The filepath has been specified. Not using the key\'s' 455 ' KEYID as the default filepath.') 456 457 # Does 'filepath' have the correct format? 458 # Ensure the arguments have the appropriate number of objects and object 459 # types, and that all dict keys are properly named. 460 # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. 461 securesystemslib.formats.PATH_SCHEMA.check_match(filepath) 462 463 # If the caller does not provide a password argument, prompt for one. 464 if password is None: # pragma: no cover 465 466 # It is safe to specify the full path of 'filepath' in the prompt and not 467 # worry about leaking sensitive information about the key's location. 468 # However, care should be taken when including the full path in exceptions 469 # and log files. 470 password = get_password('Enter a password for the Ed25519' 471 ' key (' + Fore.RED + filepath + Fore.RESET + '): ', 472 confirm=True) 473 474 else: 475 logger.debug('The password has been specified. Not prompting for one.') 476 477 # Does 'password' have the correct format? 478 securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) 479 480 # If the parent directory of filepath does not exist, 481 # create it (and all its parent directories, if necessary). 482 securesystemslib.util.ensure_parent_dir(filepath) 483 484 # Create a temporary file, write the contents of the public key, and move 485 # to final destination. 486 file_object = securesystemslib.util.TempFile() 487 488 # Generate the ed25519 public key file contents in metadata format (i.e., 489 # does not include the keyid portion). 490 keytype = ed25519_key['keytype'] 491 keyval = ed25519_key['keyval'] 492 scheme = ed25519_key['scheme'] 493 ed25519key_metadata_format = securesystemslib.keys.format_keyval_to_metadata( 494 keytype, scheme, keyval, private=False) 495 496 file_object.write(json.dumps(ed25519key_metadata_format).encode('utf-8')) 497 498 # Write the public key (i.e., 'public', which is in PEM format) to 499 # '<filepath>.pub'. (1) Create a temporary file, (2) write the contents of 500 # the public key, and (3) move to final destination. 501 # The temporary file is closed after the final move. 502 file_object.move(filepath + '.pub') 503 504 # Write the encrypted key string, conformant to 505 # 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA', to '<filepath>'. 506 file_object = securesystemslib.util.TempFile() 507 508 # Encrypt the private key if 'password' is set. 509 if len(password): 510 ed25519_key = securesystemslib.keys.encrypt_key(ed25519_key, password) 511 512 else: 513 logger.debug('An empty password was given. ' 514 'Not encrypting the private key.') 515 ed25519_key = json.dumps(ed25519_key) 516 517 # Raise 'securesystemslib.exceptions.CryptoError' if 'ed25519_key' cannot be 518 # encrypted. 519 file_object.write(ed25519_key.encode('utf-8')) 520 file_object.move(filepath) 521 522 return filepath 523 524 525 526 527def import_ed25519_publickey_from_file(filepath): 528 """ 529 <Purpose> 530 Load the ED25519 public key object (conformant to 531 'securesystemslib.formats.KEY_SCHEMA') stored in 'filepath'. Return 532 'filepath' in securesystemslib.formats.ED25519KEY_SCHEMA format. 533 534 If the key object in 'filepath' contains a private key, it is discarded. 535 536 <Arguments> 537 filepath: 538 <filepath>.pub file, a public key file. 539 540 <Exceptions> 541 securesystemslib.exceptions.FormatError, if 'filepath' is improperly 542 formatted or is an unexpected key type. 543 544 <Side Effects> 545 The contents of 'filepath' is read and saved. 546 547 <Returns> 548 An ED25519 key object conformant to 549 'securesystemslib.formats.ED25519KEY_SCHEMA'. 550 """ 551 552 # Does 'filepath' have the correct format? 553 # Ensure the arguments have the appropriate number of objects and object 554 # types, and that all dict keys are properly named. 555 # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. 556 securesystemslib.formats.PATH_SCHEMA.check_match(filepath) 557 558 # ED25519 key objects are saved in json and metadata format. Return the 559 # loaded key object in securesystemslib.formats.ED25519KEY_SCHEMA' format that 560 # also includes the keyid. 561 ed25519_key_metadata = securesystemslib.util.load_json_file(filepath) 562 ed25519_key, junk = \ 563 securesystemslib.keys.format_metadata_to_key(ed25519_key_metadata) 564 565 # Raise an exception if an unexpected key type is imported. Redundant 566 # validation of 'keytype'. 'securesystemslib.keys.format_metadata_to_key()' 567 # should have fully validated 'ed25519_key_metadata'. 568 if ed25519_key['keytype'] != 'ed25519': # pragma: no cover 569 message = 'Invalid key type loaded: ' + repr(ed25519_key['keytype']) 570 raise securesystemslib.exceptions.FormatError(message) 571 572 return ed25519_key 573 574 575 576 577 578def import_ed25519_privatekey_from_file(filepath, password=None, prompt=False): 579 """ 580 <Purpose> 581 Import the encrypted ed25519 key file in 'filepath', decrypt it, and return 582 the key object in 'securesystemslib.formats.ED25519KEY_SCHEMA' format. 583 584 The private key (may also contain the public part) is encrypted with AES 585 256 and CTR the mode of operation. The password is strengthened with 586 PBKDF2-HMAC-SHA256. 587 588 <Arguments> 589 filepath: 590 <filepath> file, an RSA encrypted key file. 591 592 password: 593 The password, or passphrase, to import the private key (i.e., the 594 encrypted key file 'filepath' must be decrypted before the ed25519 key 595 object can be returned. 596 597 prompt: 598 If True the user is prompted for a passphrase to decrypt 'filepath'. 599 Default is False. 600 601 <Exceptions> 602 securesystemslib.exceptions.FormatError, if the arguments are improperly 603 formatted or the imported key object contains an invalid key type (i.e., 604 not 'ed25519'). 605 606 securesystemslib.exceptions.CryptoError, if 'filepath' cannot be decrypted. 607 608 <Side Effects> 609 'password' is used to decrypt the 'filepath' key file. 610 611 <Returns> 612 An ed25519 key object of the form: 613 'securesystemslib.formats.ED25519KEY_SCHEMA'. 614 """ 615 616 # Does 'filepath' have the correct format? 617 # Ensure the arguments have the appropriate number of objects and object 618 # types, and that all dict keys are properly named. 619 # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. 620 securesystemslib.formats.PATH_SCHEMA.check_match(filepath) 621 622 if password and prompt: 623 raise ValueError("Passing 'password' and 'prompt' True is not allowed.") 624 625 # If 'password' was passed check format and that it is not empty. 626 if password is not None: 627 securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) 628 629 # TODO: PASSWORD_SCHEMA should be securesystemslib.schema.AnyString(min=1) 630 if not len(password): 631 raise ValueError('Password must be 1 or more characters') 632 633 elif prompt: 634 # Password confirmation disabled here, which should ideally happen only 635 # when creating encrypted key files (i.e., improve usability). 636 # It is safe to specify the full path of 'filepath' in the prompt and not 637 # worry about leaking sensitive information about the key's location. 638 # However, care should be taken when including the full path in exceptions 639 # and log files. 640 # NOTE: A user who gets prompted for a password, can only signal that the 641 # key is not encrypted by entering no password in the prompt, as opposed 642 # to a programmer who can call the function with or without a 'password'. 643 # Hence, we treat an empty password here, as if no 'password' was passed. 644 password = get_password('Enter a password for an encrypted RSA' 645 ' file \'' + Fore.RED + filepath + Fore.RESET + '\': ', 646 confirm=False) 647 648 # If user sets an empty string for the password, explicitly set the 649 # password to None, because some functions may expect this later. 650 if len(password) == 0: # pragma: no cover 651 password = None 652 653 # Finally, regardless of password, try decrypting the key, if necessary. 654 # Otherwise, load it straight from the disk. 655 with open(filepath, 'rb') as file_object: 656 json_str = file_object.read() 657 return securesystemslib.keys.\ 658 import_ed25519key_from_private_json(json_str, password=password) 659 660 661 662 663 664def generate_and_write_ecdsa_keypair(filepath=None, password=None): 665 """ 666 <Purpose> 667 Generate an ECDSA keypair, where the encrypted key (using 'password' as the 668 passphrase) is saved to <'filepath'>. The public key portion of the 669 generated ECDSA key is saved to <'filepath'>.pub. If the filepath is not 670 given, the KEYID is used as the filename and the keypair saved to the 671 current working directory. 672 673 The 'cryptography' library is currently supported. The private key is 674 encrypted according to 'cryptography's approach: "Encrypt using the best 675 available encryption for a given key's backend. This is a curated 676 encryption choice and the algorithm may change over time." 677 678 <Arguments> 679 filepath: 680 The public and private key files are saved to <filepath>.pub and 681 <filepath>, respectively. If the filepath is not given, the public and 682 private keys are saved to the current working directory as <KEYID>.pub 683 and <KEYID>. KEYID is the generated key's KEYID. 684 685 password: 686 The password, or passphrase, to encrypt the private portion of the 687 generated ECDSA key. A symmetric encryption key is derived from 688 'password', so it is not directly used. 689 690 <Exceptions> 691 securesystemslib.exceptions.FormatError, if the arguments are improperly 692 formatted. 693 694 securesystemslib.exceptions.CryptoError, if 'filepath' cannot be encrypted. 695 696 <Side Effects> 697 Writes key files to '<filepath>' and '<filepath>.pub'. 698 699 <Returns> 700 The 'filepath' of the written key. 701 """ 702 703 # Generate a new ECDSA key object. The 'cryptography' library is currently 704 # supported and performs the actual cryptographic operations. 705 ecdsa_key = securesystemslib.keys.generate_ecdsa_key() 706 707 if not filepath: 708 filepath = os.path.join(os.getcwd(), ecdsa_key['keyid']) 709 710 else: 711 logger.debug('The filepath has been specified. Not using the key\'s' 712 ' KEYID as the default filepath.') 713 714 # Does 'filepath' have the correct format? 715 # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. 716 securesystemslib.formats.PATH_SCHEMA.check_match(filepath) 717 718 # If the caller does not provide a password argument, prompt for one. 719 if password is None: # pragma: no cover 720 721 # It is safe to specify the full path of 'filepath' in the prompt and not 722 # worry about leaking sensitive information about the key's location. 723 # However, care should be taken when including the full path in exceptions 724 # and log files. 725 password = get_password('Enter a password for the ECDSA' 726 ' key (' + Fore.RED + filepath + Fore.RESET + '): ', 727 confirm=True) 728 729 else: 730 logger.debug('The password has been specified. Not prompting for one') 731 732 # Does 'password' have the correct format? 733 securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) 734 735 # If the parent directory of filepath does not exist, 736 # create it (and all its parent directories, if necessary). 737 securesystemslib.util.ensure_parent_dir(filepath) 738 739 # Create a temporary file, write the contents of the public key, and move 740 # to final destination. 741 file_object = securesystemslib.util.TempFile() 742 743 # Generate the ECDSA public key file contents in metadata format (i.e., does 744 # not include the keyid portion). 745 keytype = ecdsa_key['keytype'] 746 keyval = ecdsa_key['keyval'] 747 scheme = ecdsa_key['scheme'] 748 ecdsakey_metadata_format = securesystemslib.keys.format_keyval_to_metadata( 749 keytype, scheme, keyval, private=False) 750 751 file_object.write(json.dumps(ecdsakey_metadata_format).encode('utf-8')) 752 753 # Write the public key (i.e., 'public', which is in PEM format) to 754 # '<filepath>.pub'. (1) Create a temporary file, (2) write the contents of 755 # the public key, and (3) move to final destination. 756 file_object.move(filepath + '.pub') 757 758 # Write the encrypted key string, conformant to 759 # 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA', to '<filepath>'. 760 file_object = securesystemslib.util.TempFile() 761 # Raise 'securesystemslib.exceptions.CryptoError' if 'ecdsa_key' cannot be 762 # encrypted. 763 encrypted_key = securesystemslib.keys.encrypt_key(ecdsa_key, password) 764 file_object.write(encrypted_key.encode('utf-8')) 765 file_object.move(filepath) 766 767 return filepath 768 769 770 771 772def import_ecdsa_publickey_from_file(filepath): 773 """ 774 <Purpose> 775 Load the ECDSA public key object (conformant to 776 'securesystemslib.formats.KEY_SCHEMA') stored in 'filepath'. Return 777 'filepath' in securesystemslib.formats.ECDSAKEY_SCHEMA format. 778 779 If the key object in 'filepath' contains a private key, it is discarded. 780 781 <Arguments> 782 filepath: 783 <filepath>.pub file, a public key file. 784 785 <Exceptions> 786 securesystemslib.exceptions.FormatError, if 'filepath' is improperly 787 formatted or is an unexpected key type. 788 789 <Side Effects> 790 The contents of 'filepath' is read and saved. 791 792 <Returns> 793 An ECDSA key object conformant to 794 'securesystemslib.formats.ECDSAKEY_SCHEMA'. 795 """ 796 797 # Does 'filepath' have the correct format? 798 # Ensure the arguments have the appropriate number of objects and object 799 # types, and that all dict keys are properly named. 800 # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. 801 securesystemslib.formats.PATH_SCHEMA.check_match(filepath) 802 803 # ECDSA key objects are saved in json and metadata format. Return the 804 # loaded key object in securesystemslib.formats.ECDSAKEY_SCHEMA' format that 805 # also includes the keyid. 806 ecdsa_key_metadata = securesystemslib.util.load_json_file(filepath) 807 ecdsa_key, junk = \ 808 securesystemslib.keys.format_metadata_to_key(ecdsa_key_metadata) 809 810 # Raise an exception if an unexpected key type is imported. Redundant 811 # validation of 'keytype'. 'securesystemslib.keys.format_metadata_to_key()' 812 # should have fully validated 'ecdsa_key_metadata'. 813 if ecdsa_key['keytype'] != 'ecdsa-sha2-nistp256': # pragma: no cover 814 message = 'Invalid key type loaded: ' + repr(ecdsa_key['keytype']) 815 raise securesystemslib.exceptions.FormatError(message) 816 817 return ecdsa_key 818 819 820 821 822 823def import_ecdsa_privatekey_from_file(filepath, password=None): 824 """ 825 <Purpose> 826 Import the encrypted ECDSA key file in 'filepath', decrypt it, and return 827 the key object in 'securesystemslib.formats.ECDSAKEY_SCHEMA' format. 828 829 The 'cryptography' library is currently supported and performs the actual 830 cryptographic routine. 831 832 <Arguments> 833 filepath: 834 <filepath> file, an ECDSA encrypted key file. 835 836 password: 837 The password, or passphrase, to import the private key (i.e., the 838 encrypted key file 'filepath' must be decrypted before the ECDSA key 839 object can be returned. 840 841 <Exceptions> 842 securesystemslib.exceptions.FormatError, if the arguments are improperly 843 formatted or the imported key object contains an invalid key type (i.e., 844 not 'ecdsa-sha2-nistp256'). 845 846 securesystemslib.exceptions.CryptoError, if 'filepath' cannot be decrypted. 847 848 <Side Effects> 849 'password' is used to decrypt the 'filepath' key file. 850 851 <Returns> 852 An ECDSA key object of the form: 'securesystemslib.formats.ECDSAKEY_SCHEMA'. 853 """ 854 855 # Does 'filepath' have the correct format? 856 # Ensure the arguments have the appropriate number of objects and object 857 # types, and that all dict keys are properly named. 858 # Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch. 859 securesystemslib.formats.PATH_SCHEMA.check_match(filepath) 860 861 # If the caller does not provide a password argument, prompt for one. 862 # Password confirmation disabled here, which should ideally happen only 863 # when creating encrypted key files (i.e., improve usability). 864 if password is None: # pragma: no cover 865 866 # It is safe to specify the full path of 'filepath' in the prompt and not 867 # worry about leaking sensitive information about the key's location. 868 # However, care should be taken when including the full path in exceptions 869 # and log files. 870 password = get_password('Enter a password for the encrypted ECDSA' 871 ' key (' + Fore.RED + filepath + Fore.RESET + '): ', 872 confirm=False) 873 874 # Does 'password' have the correct format? 875 securesystemslib.formats.PASSWORD_SCHEMA.check_match(password) 876 877 # Store the encrypted contents of 'filepath' prior to calling the decryption 878 # routine. 879 encrypted_key = None 880 881 with open(filepath, 'rb') as file_object: 882 encrypted_key = file_object.read() 883 884 # Decrypt the loaded key file, calling the 'cryptography' library to generate 885 # the derived encryption key from 'password'. Raise 886 # 'securesystemslib.exceptions.CryptoError' if the decryption fails. 887 key_object = securesystemslib.keys.decrypt_key(encrypted_key.decode('utf-8'), 888 password) 889 890 # Raise an exception if an unexpected key type is imported. 891 if key_object['keytype'] != 'ecdsa-sha2-nistp256': 892 message = 'Invalid key type loaded: ' + repr(key_object['keytype']) 893 raise securesystemslib.exceptions.FormatError(message) 894 895 # Add "keyid_hash_algorithms" so that equal ecdsa keys with different keyids 896 # can be associated using supported keyid_hash_algorithms. 897 key_object['keyid_hash_algorithms'] = \ 898 securesystemslib.settings.HASH_ALGORITHMS 899 900 return key_object 901 902 903 904if __name__ == '__main__': 905 # The interactive sessions of the documentation strings can 906 # be tested by running interface.py as a standalone module: 907 # $ python interface.py. 908 import doctest 909 doctest.testmod() 910