1#!/usr/bin/env python
2
3"""
4<Program Name>
5  pyca_crypto_keys.py
6
7<Author>
8  Vladimir Diaz <vladimir.v.diaz@gmail.com>
9
10<Started>
11  June 3, 2015.
12
13<Copyright>
14  See LICENSE for licensing information.
15
16<Purpose>
17  The goal of this module is to support public-key and general-purpose
18  cryptography through the pyca/cryptography (available as 'cryptography' on
19  pypi) library.
20
21  The RSA-related functions provided include:
22  generate_rsa_public_and_private()
23  create_rsa_signature()
24  verify_rsa_signature()
25  create_rsa_encrypted_pem()
26  create_rsa_public_and_private_from_pem()
27
28  The general-purpose functions include:
29  encrypt_key()
30  decrypt_key()
31
32  pyca/cryptography performs the actual cryptographic operations and the
33  functions listed above can be viewed as the easy-to-use public interface.
34
35  https://pypi.python.org/pypi/cryptography/
36  https://github.com/pyca/cryptography
37
38  https://en.wikipedia.org/wiki/RSA_(algorithm)
39  https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
40  https://en.wikipedia.org/wiki/PBKDF
41  http://en.wikipedia.org/wiki/Scrypt
42
43  TUF key files are encrypted with the AES-256-CTR-Mode symmetric key
44  algorithm.  User passwords are strengthened with PBKDF2, currently set to
45  100,000 passphrase iterations.  The previous evpy implementation used 1,000
46  iterations.
47
48  PEM-encrypted RSA key files use the Triple Data Encryption Algorithm (3DES),
49  and Cipher-block chaining (CBC) for the mode of operation.  Password-Based
50  Key Derivation Function 1 (PBKF1) + MD5.
51 """
52
53# Help with Python 3 compatibility, where the print statement is a function, an
54# implicit relative import is invalid, and the '/' operator performs true
55# division.  Example:  print 'hello world' raises a 'SyntaxError' exception.
56from __future__ import print_function
57from __future__ import absolute_import
58from __future__ import division
59from __future__ import unicode_literals
60
61import os
62import binascii
63import json
64
65# Import pyca/cryptography routines needed to generate and load cryptographic
66# keys in PEM format.
67from cryptography.hazmat.primitives import serialization
68from cryptography.hazmat.backends.interfaces import PEMSerializationBackend
69from cryptography.hazmat.primitives.serialization import load_pem_private_key
70from cryptography.hazmat.backends import default_backend
71
72# Import Exception classes need to catch pyca/cryptography exceptions.
73import cryptography.exceptions
74
75# 'cryptography.hazmat.primitives.asymmetric' (i.e., pyca/cryptography's
76# public-key cryptography modules) supports algorithms like the Digital
77# Signature Algorithm (DSA) and the ECDSA (Elliptic Curve Digital Signature
78# Algorithm) encryption system.  The 'rsa' module module is needed here to
79# generate RSA keys and PS
80from cryptography.hazmat.primitives.asymmetric import rsa
81
82# pyca/cryptography requires hash objects to generate PKCS#1 PSS
83# signatures (i.e., padding.PSS).  The 'hmac' module is needed to verify
84# ciphertexts in encrypted key files.
85from cryptography.hazmat.primitives import hashes
86from cryptography.hazmat.primitives import hmac
87
88# RSA's probabilistic signature scheme with appendix (RSASSA-PSS).
89# PKCS#1 v1.5 is available for compatibility with existing applications, but
90# RSASSA-PSS is encouraged for newer applications.  RSASSA-PSS generates
91# a random salt to ensure the signature generated is probabilistic rather than
92# deterministic (e.g., PKCS#1 v1.5).
93# http://en.wikipedia.org/wiki/RSA-PSS#Schemes
94# https://tools.ietf.org/html/rfc3447#section-8.1
95# The 'padding' module is needed for PSS signatures.
96from cryptography.hazmat.primitives.asymmetric import padding
97
98# Import pyca/cryptography's Key Derivation Function (KDF) module.
99# 'securesystemslib.keys.py' needs this module to derive a secret key according
100# to the Password-Based Key Derivation Function 2 specification.  The derived
101# key is used as the symmetric key to encrypt TUF key information.
102# PKCS#5 v2.0 PBKDF2 specification: http://tools.ietf.org/html/rfc2898#section-5.2
103from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
104
105# pyca/cryptography's AES implementation available in 'ciphers.Cipher. and
106# 'ciphers.algorithms'.  AES is a symmetric key algorithm that operates on
107# fixed block sizes of 128-bits.
108# https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
109from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
110
111# The mode of operation is presently set to CTR (CounTeR Mode) for symmetric
112# block encryption (AES-256, where the symmetric key is 256 bits).  'modes' can
113# be used as an argument to 'ciphers.Cipher' to specify the mode of operation
114# for the block cipher.  The initial random block, or initialization vector
115# (IV), can be set to begin the process of incrementing the 128-bit blocks and
116# allowing the AES algorithm to perform cipher block operations on them.
117from cryptography.hazmat.primitives.ciphers import modes
118
119import securesystemslib.exceptions
120import securesystemslib.formats
121import securesystemslib.util
122
123# Extract/reference the cryptography library settings.
124import securesystemslib.settings
125
126# Recommended RSA key sizes:
127# http://www.emc.com/emc-plus/rsa-labs/historical/twirl-and-rsa-key-size.htm#table1
128# According to the document above, revised May 6, 2003, RSA keys of size 3072
129# provide security through 2031 and beyond.
130_DEFAULT_RSA_KEY_BITS = 3072
131
132# The delimiter symbol used to separate the different sections of encrypted
133# files (i.e., salt, iterations, hmac, IV, ciphertext).  This delimiter is
134# arbitrarily chosen and should not occur in the hexadecimal representations of
135# the fields it is separating.
136_ENCRYPTION_DELIMITER = '@@@@'
137
138# AES key size.  Default key size = 32 bytes = AES-256.
139_AES_KEY_SIZE = 32
140
141# Default salt size, in bytes.  A 128-bit salt (i.e., a random sequence of data
142# to protect against attacks that use precomputed rainbow tables to crack
143# password hashes) is generated for PBKDF2.
144_SALT_SIZE = 16
145
146# Default PBKDF2 passphrase iterations.  The current "good enough" number
147# of passphrase iterations.  We recommend that important keys, such as root,
148# be kept offline.  'settings.PBKDF2_ITERATIONS' should increase as CPU
149# speeds increase, set here at 100,000 iterations by default (in 2013).
150# Repository maintainers may opt to modify the default setting according to
151# their security needs and computational restrictions.  A strong user password
152# is still important.  Modifying the number of iterations will result in a new
153# derived key+PBDKF2 combination if the key is loaded and re-saved, overriding
154# any previous iteration setting used by the old '<keyid>.key'.
155# https://en.wikipedia.org/wiki/PBKDF2
156_PBKDF2_ITERATIONS = securesystemslib.settings.PBKDF2_ITERATIONS
157
158
159
160def generate_rsa_public_and_private(bits=_DEFAULT_RSA_KEY_BITS):
161  """
162  <Purpose>
163    Generate public and private RSA keys with modulus length 'bits'.  The
164    public and private keys returned conform to
165    'securesystemslib.formats.PEMRSA_SCHEMA' and have the form:
166
167    '-----BEGIN RSA PUBLIC KEY----- ...'
168
169    or
170
171    '-----BEGIN RSA PRIVATE KEY----- ...'
172
173    The public and private keys are returned as strings in PEM format.
174
175    'generate_rsa_public_and_private()' enforces a minimum key size of 2048
176    bits.  If 'bits' is unspecified, a 3072-bit RSA key is generated, which is
177    the key size recommended by TUF.
178
179    >>> public, private = generate_rsa_public_and_private(2048)
180    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(public)
181    True
182    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(private)
183    True
184
185  <Arguments>
186    bits:
187      The key size, or key length, of the RSA key.  'bits' must be 2048, or
188      greater.  'bits' defaults to 3072 if not specified.
189
190  <Exceptions>
191    securesystemslib.exceptions.FormatError, if 'bits' does not contain the
192    correct format.
193
194  <Side Effects>
195    The RSA keys are generated from pyca/cryptography's
196    rsa.generate_private_key() function.
197
198  <Returns>
199    A (public, private) tuple containing the RSA keys in PEM format.
200  """
201
202  # Does 'bits' have the correct format?
203  # This check will ensure 'bits' conforms to
204  # 'securesystemslib.formats.RSAKEYBITS_SCHEMA'.  'bits' must be an integer
205  # object, with a minimum value of 2048.  Raise
206  # 'securesystemslib.exceptions.FormatError' if the check fails.
207  securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match(bits)
208
209  # Generate the public and private RSA keys.  The pyca/cryptography 'rsa'
210  # module performs the actual key generation.  The 'bits' argument is used,
211  # and a 2048-bit minimum is enforced by
212  # securesystemslib.formats.RSAKEYBITS_SCHEMA.check_match().
213  private_key = rsa.generate_private_key(public_exponent=65537, key_size=bits,
214      backend=default_backend())
215
216  # Extract the public & private halves of the RSA key and generate their
217  # PEM-formatted representations.  Return the key pair as a (public, private)
218  # tuple, where each RSA is a string in PEM format.
219  private_pem = private_key.private_bytes(encoding=serialization.Encoding.PEM,
220      format=serialization.PrivateFormat.TraditionalOpenSSL,
221      encryption_algorithm=serialization.NoEncryption())
222
223  # Need to generate the public pem from the private key before serialization
224  # to PEM.
225  public_key = private_key.public_key()
226  public_pem = public_key.public_bytes(encoding=serialization.Encoding.PEM,
227      format=serialization.PublicFormat.SubjectPublicKeyInfo)
228
229  return public_pem.decode('utf-8'), private_pem.decode('utf-8')
230
231
232
233
234
235def create_rsa_signature(private_key, data, scheme='rsassa-pss-sha256'):
236  """
237  <Purpose>
238    Generate a 'scheme' signature.  The signature, and the signature scheme
239    used, is returned as a (signature, scheme) tuple.
240
241    The signing process will use 'private_key' to generate the signature of
242    'data'.
243
244    RFC3447 - RSASSA-PSS
245    http://www.ietf.org/rfc/rfc3447.txt
246
247    >>> public, private = generate_rsa_public_and_private(2048)
248    >>> data = 'The quick brown fox jumps over the lazy dog'.encode('utf-8')
249    >>> scheme = 'rsassa-pss-sha256'
250    >>> signature, scheme = create_rsa_signature(private, data, scheme)
251    >>> securesystemslib.formats.NAME_SCHEMA.matches(scheme)
252    True
253    >>> scheme == 'rsassa-pss-sha256'
254    True
255    >>> securesystemslib.formats.PYCACRYPTOSIGNATURE_SCHEMA.matches(signature)
256    True
257
258  <Arguments>
259    private_key:
260      The private RSA key, a string in PEM format.
261
262    data:
263      Data (string) used by create_rsa_signature() to generate the signature.
264
265    scheme:
266      The signature scheme used to generate the signature.
267
268  <Exceptions>
269    securesystemslib.exceptions.FormatError, if 'private_key' is improperly
270    formatted.
271
272    ValueError, if 'private_key' is unset.
273
274    securesystemslib.exceptions.CryptoError, if the signature cannot be
275    generated.
276
277  <Side Effects>
278    pyca/cryptography's 'RSAPrivateKey.signer()' called to generate the
279    signature.
280
281  <Returns>
282    A (signature, scheme) tuple, where the signature is a string and the scheme
283    is one of the supported RSA signature schemes. For example:
284    'rsassa-pss-sha256'.
285  """
286
287  # Does the arguments have the correct format?
288  # If not, raise 'securesystemslib.exceptions.FormatError' if any of the
289  # checks fail.
290  securesystemslib.formats.PEMRSA_SCHEMA.check_match(private_key)
291  securesystemslib.formats.DATA_SCHEMA.check_match(data)
292  securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(scheme)
293
294  # Signing 'data' requires a private key.  'rsassa-pss-sha256' is the only
295  # currently supported signature scheme.
296  signature = None
297
298  # Verify the signature, but only if the private key has been set.  The
299  # private key is a NULL string if unset.  Although it may be clearer to
300  # explicitly check that 'private_key' is not '', we can/should check for a
301  # value and not compare identities with the 'is' keyword.  Up to this point
302  # 'private_key' has variable size and can be an empty string.
303  if len(private_key):
304
305    # An if-clause isn't strictly needed here, since 'rsasssa-pss-sha256' is
306    # the only currently supported RSA scheme.  Nevertheless, include the
307    # conditional statement to accomodate future schemes that might be added.
308    if scheme == 'rsassa-pss-sha256':
309      # Generate an RSSA-PSS signature.  Raise
310      # 'securesystemslib.exceptions.CryptoError' for any of the expected
311      # exceptions raised by pyca/cryptography.
312      try:
313        # 'private_key' (in PEM format) must first be converted to a
314        # pyca/cryptography private key object before a signature can be
315        # generated.
316        private_key_object = load_pem_private_key(private_key.encode('utf-8'),
317            password=None, backend=default_backend())
318
319        signature = private_key_object.sign(
320            data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
321            salt_length=hashes.SHA256().digest_size), hashes.SHA256())
322
323      # If the PEM data could not be decrypted, or if its structure could not
324      # be decoded successfully.
325      except ValueError:
326        raise securesystemslib.exceptions.CryptoError('The private key'
327          ' (in PEM format) could not be deserialized.')
328
329      # 'TypeError' is raised if a password was given and the private key was
330      # not encrypted, or if the key was encrypted but no password was
331      # supplied.  Note: A passphrase or password is not used when generating
332      # 'private_key', since it should not be encrypted.
333      except TypeError:
334        raise securesystemslib.exceptions.CryptoError('The private key was'
335          ' unexpectedly encrypted.')
336
337      # 'cryptography.exceptions.UnsupportedAlgorithm' is raised if the
338      # serialized key is of a type that is not supported by the backend, or if
339      # the key is encrypted with a symmetric cipher that is not supported by
340      # the backend.
341      except cryptography.exceptions.UnsupportedAlgorithm: #pragma: no cover
342        raise securesystemslib.exceptions.CryptoError('The private key is'
343          ' encrypted with an unsupported algorithm.')
344
345    # The RSA_SCHEME_SCHEMA.check_match() above should have validated 'scheme'.
346    # This is a defensive check check..
347    else: #pragma: no cover
348      raise securesystemslib.exceptions.UnsupportedAlgorithmError('Unsupported'
349        ' signature scheme is specified: ' + repr(scheme))
350
351  else:
352    raise ValueError('The required private key is unset.')
353
354  return signature, scheme
355
356
357
358
359
360def verify_rsa_signature(signature, signature_scheme, public_key, data):
361  """
362  <Purpose>
363    Determine whether the corresponding private key of 'public_key' produced
364    'signature'.  verify_signature() will use the public key, signature scheme,
365    and 'data' to complete the verification.
366
367    >>> public, private = generate_rsa_public_and_private(2048)
368    >>> data = b'The quick brown fox jumps over the lazy dog'
369    >>> scheme = 'rsassa-pss-sha256'
370    >>> signature, scheme = create_rsa_signature(private, data, scheme)
371    >>> verify_rsa_signature(signature, scheme, public, data)
372    True
373    >>> verify_rsa_signature(signature, scheme, public, b'bad_data')
374    False
375
376  <Arguments>
377    signature:
378      A signature, as a string.  This is the signature returned
379      by create_rsa_signature().
380
381    signature_scheme:
382      A string that indicates the signature scheme used to generate
383      'signature'.  'rsassa-pss-sha256' is currently supported.
384
385    public_key:
386      The RSA public key, a string in PEM format.
387
388    data:
389      Data used by securesystemslib.keys.create_signature() to generate
390      'signature'.  'data' (a string) is needed here to verify 'signature'.
391
392  <Exceptions>
393    securesystemslib.exceptions.FormatError, if 'signature',
394    'signature_scheme', 'public_key', or 'data' are improperly formatted.
395
396    securesystemslib.exceptions.UnsupportedAlgorithmError, if the signature
397    scheme used by 'signature' is not one supported by
398    securesystemslib.keys.create_signature().
399
400    securesystemslib.exceptions.CryptoError, if the private key cannot be
401    decoded or its key type is unsupported.
402
403  <Side Effects>
404    pyca/cryptography's RSAPublicKey.verifier() called to do the actual
405    verification.
406
407   <Returns>
408    Boolean.  True if the signature is valid, False otherwise.
409  """
410
411  # Does 'public_key' have the correct format?
412  # This check will ensure 'public_key' conforms to
413  # 'securesystemslib.formats.PEMRSA_SCHEMA'.  Raise
414  # 'securesystemslib.exceptions.FormatError' if the check fails.
415  securesystemslib.formats.PEMRSA_SCHEMA.check_match(public_key)
416
417  # Does 'signature_scheme' have the correct format?
418  securesystemslib.formats.RSA_SCHEME_SCHEMA.check_match(signature_scheme)
419
420  # Does 'signature' have the correct format?
421  securesystemslib.formats.PYCACRYPTOSIGNATURE_SCHEMA.check_match(signature)
422
423  # What about 'data'?
424  securesystemslib.formats.DATA_SCHEMA.check_match(data)
425
426  # Verify whether the private key of 'public_key' produced 'signature'.
427  # Before returning the 'valid_signature' Boolean result, ensure 'RSASSA-PSS'
428  # was used as the signature scheme.
429  valid_signature = False
430
431  # Verify the RSASSA-PSS signature with pyca/cryptography.
432  try:
433    public_key_object = serialization.load_pem_public_key(public_key.encode('utf-8'),
434        backend=default_backend())
435
436    # verify() raises 'cryptography.exceptions.InvalidSignature' if the
437    # signature is invalid. 'salt_length' is set to the digest size of the
438    # hashing algorithm.
439    try:
440      public_key_object.verify(signature, data,
441          padding.PSS(mgf=padding.MGF1(hashes.SHA256()),
442          salt_length=hashes.SHA256().digest_size),
443          hashes.SHA256())
444      return True
445
446    except cryptography.exceptions.InvalidSignature:
447      return False
448
449  # Raised by load_pem_public_key().
450  except (ValueError, cryptography.exceptions.UnsupportedAlgorithm) as e:
451    raise securesystemslib.exceptions.CryptoError('The PEM could not be'
452      ' decoded successfully, or contained an unsupported key type: ' + str(e))
453
454
455def create_rsa_encrypted_pem(private_key, passphrase):
456  """
457  <Purpose>
458    Return a string in PEM format (TraditionalOpenSSL), where the private part
459    of the RSA key is encrypted using the best available encryption for a given
460    key's backend. This is a curated (by cryptography.io) encryption choice and
461    the algorithm may change over time.
462
463    c.f. cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/
464        #cryptography.hazmat.primitives.serialization.BestAvailableEncryption
465
466    >>> public, private = generate_rsa_public_and_private(2048)
467    >>> passphrase = 'secret'
468    >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase)
469    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(encrypted_pem)
470    True
471
472  <Arguments>
473    private_key:
474      The private key string in PEM format.
475
476    passphrase:
477      The passphrase, or password, to encrypt the private part of the RSA
478      key.
479
480  <Exceptions>
481    securesystemslib.exceptions.FormatError, if the arguments are improperly
482        formatted.
483
484    securesystemslib.exceptions.CryptoError, if the passed RSA key cannot be
485        deserialized by pyca cryptography.
486
487    ValueError, if 'private_key' is unset.
488
489
490  <Returns>
491    A string in PEM format (TraditionalOpenSSL), where the private RSA key is
492    encrypted. Conforms to 'securesystemslib.formats.PEMRSA_SCHEMA'.
493  """
494
495  # This check will ensure 'private_key' has the appropriate number
496  # of objects and object types, and that all dict keys are properly named.
497  # Raise 'securesystemslib.exceptions.FormatError' if the check fails.
498  securesystemslib.formats.PEMRSA_SCHEMA.check_match(private_key)
499
500  # Does 'passphrase' have the correct format?
501  securesystemslib.formats.PASSWORD_SCHEMA.check_match(passphrase)
502
503  # 'private_key' may still be a NULL string after the
504  # 'securesystemslib.formats.PEMRSA_SCHEMA' so we need an additional check
505  if len(private_key):
506    try:
507      private_key = load_pem_private_key(private_key.encode('utf-8'),
508          password=None, backend=default_backend())
509    except ValueError:
510      raise securesystemslib.exceptions.CryptoError('The private key'
511          ' (in PEM format) could not be deserialized.')
512
513  else:
514    raise ValueError('The required private key is unset.')
515
516  encrypted_pem = private_key.private_bytes(
517      encoding=serialization.Encoding.PEM,
518      format=serialization.PrivateFormat.TraditionalOpenSSL,
519      encryption_algorithm=serialization.BestAvailableEncryption(
520      passphrase.encode('utf-8')))
521
522  return encrypted_pem.decode()
523
524
525
526
527
528def create_rsa_public_and_private_from_pem(pem, passphrase=None):
529  """
530  <Purpose>
531    Generate public and private RSA keys from an optionally encrypted PEM.  The
532    public and private keys returned conform to
533    'securesystemslib.formats.PEMRSA_SCHEMA' and have the form:
534
535    '-----BEGIN RSA PUBLIC KEY----- ... -----END RSA PUBLIC KEY-----'
536
537    and
538
539    '-----BEGIN RSA PRIVATE KEY----- ...-----END RSA PRIVATE KEY-----'
540
541    The public and private keys are returned as strings in PEM format.
542
543    In case the private key part of 'pem' is encrypted  pyca/cryptography's
544    load_pem_private_key() method is passed passphrase.  In the default case
545    here, pyca/cryptography will decrypt with a PBKDF1+MD5
546    strengthened'passphrase', and 3DES with CBC mode for encryption/decryption.
547    Alternatively, key data may be encrypted with AES-CTR-Mode and the
548    passphrase strengthened with PBKDF2+SHA256, although this method is used
549    only with TUF encrypted key files.
550
551    >>> public, private = generate_rsa_public_and_private(2048)
552    >>> passphrase = 'secret'
553    >>> encrypted_pem = create_rsa_encrypted_pem(private, passphrase)
554    >>> returned_public, returned_private = \
555    create_rsa_public_and_private_from_pem(encrypted_pem, passphrase)
556    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(returned_public)
557    True
558    >>> securesystemslib.formats.PEMRSA_SCHEMA.matches(returned_private)
559    True
560    >>> public == returned_public
561    True
562    >>> private == returned_private
563    True
564
565  <Arguments>
566    pem:
567      A byte string in PEM format, where the private key can be encrypted.
568      It has the form:
569
570      '-----BEGIN RSA PRIVATE KEY-----\n
571      Proc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC ...'
572
573    passphrase: (optional)
574      The passphrase, or password, to decrypt the private part of the RSA
575      key.  'passphrase' is not directly used as the encryption key, instead
576      it is used to derive a stronger symmetric key.
577
578  <Exceptions>
579    securesystemslib.exceptions.FormatError, if the arguments are improperly
580    formatted.
581
582    securesystemslib.exceptions.CryptoError, if the public and private RSA keys
583    cannot be generated from 'pem', or exported in PEM format.
584
585  <Side Effects>
586    pyca/cryptography's 'serialization.load_pem_private_key()' called to
587    perform the actual conversion from an encrypted RSA private key to
588    PEM format.
589
590  <Returns>
591    A (public, private) tuple containing the RSA keys in PEM format.
592  """
593
594  # Does 'encryped_pem' have the correct format?
595  # This check will ensure 'pem' has the appropriate number
596  # of objects and object types, and that all dict keys are properly named.
597  # Raise 'securesystemslib.exceptions.FormatError' if the check fails.
598  securesystemslib.formats.PEMRSA_SCHEMA.check_match(pem)
599
600  # If passed, does 'passphrase' have the correct format?
601  if passphrase is not None:
602    securesystemslib.formats.PASSWORD_SCHEMA.check_match(passphrase)
603    passphrase = passphrase.encode('utf-8')
604
605  # Generate a pyca/cryptography key object from 'pem'.  The generated
606  # pyca/cryptography key contains the required export methods needed to
607  # generate the PEM-formatted representations of the public and private RSA
608  # key.
609  try:
610    private_key = load_pem_private_key(pem.encode('utf-8'),
611        passphrase, backend=default_backend())
612
613  # pyca/cryptography's expected exceptions for 'load_pem_private_key()':
614  # ValueError: If the PEM data could not be decrypted.
615  # (possibly because the passphrase is wrong)."
616  # TypeError: If a password was given and the private key was not encrypted.
617  # Or if the key was encrypted but no password was supplied.
618  # UnsupportedAlgorithm: If the private key (or if the key is encrypted with
619  # an unsupported symmetric cipher) is not supported by the backend.
620  except (ValueError, TypeError, cryptography.exceptions.UnsupportedAlgorithm) as e:
621    # Raise 'securesystemslib.exceptions.CryptoError' and pyca/cryptography's
622    # exception message.  Avoid propogating pyca/cryptography's exception trace
623    # to avoid revealing sensitive error.
624    raise securesystemslib.exceptions.CryptoError('RSA (public, private) tuple'
625      ' cannot be generated from the encrypted PEM string: ' + str(e))
626
627  # Export the public and private halves of the pyca/cryptography RSA key
628  # object.  The (public, private) tuple returned contains the public and
629  # private RSA keys in PEM format, as strings.
630  # Extract the public & private halves of the RSA key and generate their
631  # PEM-formatted representations.  Return the key pair as a (public, private)
632  # tuple, where each RSA is a string in PEM format.
633  private_pem = private_key.private_bytes(encoding=serialization.Encoding.PEM,
634      format=serialization.PrivateFormat.TraditionalOpenSSL,
635      encryption_algorithm=serialization.NoEncryption())
636
637  # Need to generate the public key from the private one before serializing
638  # to PEM format.
639  public_key = private_key.public_key()
640  public_pem = public_key.public_bytes(encoding=serialization.Encoding.PEM,
641      format=serialization.PublicFormat.SubjectPublicKeyInfo)
642
643  return public_pem.decode(), private_pem.decode()
644
645
646
647
648
649def encrypt_key(key_object, password):
650  """
651  <Purpose>
652    Return a string containing 'key_object' in encrypted form. Encrypted
653    strings may be safely saved to a file.  The corresponding decrypt_key()
654    function can be applied to the encrypted string to restore the original key
655    object.  'key_object' is a TUF key (e.g., RSAKEY_SCHEMA,
656    ED25519KEY_SCHEMA).  This function calls the pyca/cryptography library to
657    perform the encryption and derive a suitable encryption key.
658
659    Whereas an encrypted PEM file uses the Triple Data Encryption Algorithm
660    (3DES), the Cipher-block chaining (CBC) mode of operation, and the Password
661    Based Key Derivation Function 1 (PBKF1) + MD5 to strengthen 'password',
662    encrypted TUF keys use AES-256-CTR-Mode and passwords strengthened with
663    PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in
664    'settings.PBKDF2_ITERATIONS' by the user).
665
666    http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
667    http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29
668    https://en.wikipedia.org/wiki/PBKDF2
669
670    >>> ed25519_key = {'keytype': 'ed25519', \
671                       'scheme': 'ed25519', \
672                       'keyid': \
673          'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \
674                       'keyval': {'public': \
675          '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \
676                                  'private': \
677          '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}}
678    >>> passphrase = 'secret'
679    >>> encrypted_key = encrypt_key(ed25519_key, passphrase)
680    >>> securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.matches(encrypted_key.encode('utf-8'))
681    True
682
683  <Arguments>
684    key_object:
685      The TUF key object that should contain the private portion of the ED25519
686      key.
687
688    password:
689      The password, or passphrase, to encrypt the private part of the RSA
690      key.  'password' is not used directly as the encryption key, a stronger
691      encryption key is derived from it.
692
693  <Exceptions>
694    securesystemslib.exceptions.FormatError, if any of the arguments are
695    improperly formatted or 'key_object' does not contain the private portion
696    of the key.
697
698    securesystemslib.exceptions.CryptoError, if an Ed25519 key in encrypted TUF
699    format cannot be created.
700
701  <Side Effects>
702    pyca/Cryptography cryptographic operations called to perform the actual
703    encryption of 'key_object'.  'password' used to derive a suitable
704    encryption key.
705
706  <Returns>
707    An encrypted string in 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA' format.
708  """
709
710  # Do the arguments have the correct format?
711  # Ensure the arguments have the appropriate number of objects and object
712  # types, and that all dict keys are properly named.
713  # Raise 'securesystemslib.exceptions.FormatError' if the check fails.
714  securesystemslib.formats.ANYKEY_SCHEMA.check_match(key_object)
715
716  # Does 'password' have the correct format?
717  securesystemslib.formats.PASSWORD_SCHEMA.check_match(password)
718
719  # Ensure the private portion of the key is included in 'key_object'.
720  if 'private' not in key_object['keyval'] or not key_object['keyval']['private']:
721    raise securesystemslib.exceptions.FormatError('Key object does not contain'
722      ' a private part.')
723
724  # Derive a key (i.e., an appropriate encryption key and not the
725  # user's password) from the given 'password'.  Strengthen 'password' with
726  # PBKDF2-HMAC-SHA256 (100K iterations by default, but may be overriden in
727  # 'settings.PBKDF2_ITERATIONS' by the user).
728  salt, iterations, derived_key = _generate_derived_key(password)
729
730  # Store the derived key info in a dictionary, the object expected
731  # by the non-public _encrypt() routine.
732  derived_key_information = {'salt': salt, 'iterations': iterations,
733                             'derived_key': derived_key}
734
735  # Convert the key object to json string format and encrypt it with the
736  # derived key.
737  encrypted_key = _encrypt(json.dumps(key_object), derived_key_information)
738
739  return encrypted_key
740
741
742
743
744
745def decrypt_key(encrypted_key, password):
746  """
747  <Purpose>
748    Return a string containing 'encrypted_key' in non-encrypted form.
749    The decrypt_key() function can be applied to the encrypted string to restore
750    the original key object, a TUF key (e.g., RSAKEY_SCHEMA, ED25519KEY_SCHEMA).
751    This function calls the appropriate cryptography module (i.e.,
752    pyca_crypto_keys.py) to perform the decryption.
753
754    Encrypted TUF keys use AES-256-CTR-Mode and passwords strengthened with
755    PBKDF2-HMAC-SHA256 (100K iterations be default, but may be overriden in
756    'settings.py' by the user).
757
758    http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
759    http://en.wikipedia.org/wiki/CTR_mode#Counter_.28CTR.29
760    https://en.wikipedia.org/wiki/PBKDF2
761
762    >>> ed25519_key = {'keytype': 'ed25519', \
763                       'scheme': 'ed25519', \
764                       'keyid': \
765          'd62247f817883f593cf6c66a5a55292488d457bcf638ae03207dbbba9dbe457d', \
766                       'keyval': {'public': \
767          '74addb5ad544a4306b34741bc1175a3613a8d7dc69ff64724243efdec0e301ad', \
768                                  'private': \
769          '1f26964cc8d4f7ee5f3c5da2fbb7ab35811169573ac367b860a537e47789f8c4'}}
770    >>> passphrase = 'secret'
771    >>> encrypted_key = encrypt_key(ed25519_key, passphrase)
772    >>> decrypted_key = decrypt_key(encrypted_key.encode('utf-8'), passphrase)
773    >>> securesystemslib.formats.ED25519KEY_SCHEMA.matches(decrypted_key)
774    True
775    >>> decrypted_key == ed25519_key
776    True
777
778  <Arguments>
779    encrypted_key:
780      An encrypted TUF key (additional data is also included, such as salt,
781      number of password iterations used for the derived encryption key, etc)
782      of the form 'securesystemslib.formats.ENCRYPTEDKEY_SCHEMA'.
783      'encrypted_key' should have been generated with encrypted_key().
784
785    password:
786      The password, or passphrase, to encrypt the private part of the RSA
787      key.  'password' is not used directly as the encryption key, a stronger
788      encryption key is derived from it.
789
790  <Exceptions>
791    securesystemslib.exceptions.FormatError, if the arguments are improperly
792    formatted.
793
794    securesystemslib.exceptions.CryptoError, if a TUF key cannot be decrypted
795    from 'encrypted_key'.
796
797    securesystemslib.exceptions.Error, if a valid TUF key object is not found in
798    'encrypted_key'.
799
800  <Side Effects>
801    The pyca/cryptography is library called to perform the actual decryption
802    of 'encrypted_key'.  The key derivation data stored in 'encrypted_key' is
803    used to re-derive the encryption/decryption key.
804
805  <Returns>
806    The decrypted key object in 'securesystemslib.formats.ANYKEY_SCHEMA' format.
807  """
808
809  # Do the arguments have the correct format?
810  # Ensure the arguments have the appropriate number of objects and object
811  # types, and that all dict keys are properly named.
812  # Raise 'securesystemslib.exceptions.FormatError' if the check fails.
813  securesystemslib.formats.ENCRYPTEDKEY_SCHEMA.check_match(encrypted_key)
814
815  # Does 'password' have the correct format?
816  securesystemslib.formats.PASSWORD_SCHEMA.check_match(password)
817
818  # Decrypt 'encrypted_key', using 'password' (and additional key derivation
819  # data like salts and password iterations) to re-derive the decryption key.
820  json_data = _decrypt(encrypted_key, password)
821
822  # Raise 'securesystemslib.exceptions.Error' if 'json_data' cannot be
823  # deserialized to a valid 'securesystemslib.formats.ANYKEY_SCHEMA' key
824  # object.
825  key_object = securesystemslib.util.load_json_string(json_data.decode())
826
827  return key_object
828
829
830
831
832
833def _generate_derived_key(password, salt=None, iterations=None):
834  """
835  Generate a derived key by feeding 'password' to the Password-Based Key
836  Derivation Function (PBKDF2).  pyca/cryptography's PBKDF2 implementation is
837  used in this module.  'salt' may be specified so that a previous derived key
838  may be regenerated, otherwise '_SALT_SIZE' is used by default.  'iterations'
839  is the number of SHA-256 iterations to perform, otherwise
840  '_PBKDF2_ITERATIONS' is used by default.
841  """
842
843  # Use pyca/cryptography's default backend (e.g., openSSL, CommonCrypto, etc.)
844  # The default backend is not fixed and can be changed by pyca/cryptography
845  # over time.
846  backend = default_backend()
847
848  # If 'salt' and 'iterations' are unspecified, a new derived key is generated.
849  # If specified, a deterministic key is derived according to the given
850  # 'salt' and 'iterrations' values.
851  if salt is None:
852    salt = os.urandom(_SALT_SIZE)
853
854  if iterations is None:
855    iterations = _PBKDF2_ITERATIONS
856
857  # Derive an AES key with PBKDF2.  The  'length' is the desired key length of
858  # the derived key.
859  pbkdf_object = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt,
860       iterations=iterations, backend=backend)
861
862  derived_key = pbkdf_object.derive(password.encode('utf-8'))
863
864  return salt, iterations, derived_key
865
866
867
868
869
870def _encrypt(key_data, derived_key_information):
871  """
872  Encrypt 'key_data' using the Advanced Encryption Standard (AES-256) algorithm.
873  'derived_key_information' should contain a key strengthened by PBKDF2.  The
874  key size is 256 bits and AES's mode of operation is set to CTR (CounTeR Mode).
875  The HMAC of the ciphertext is generated to ensure the ciphertext has not been
876  modified.
877
878  'key_data' is the JSON string representation of the key.  In the case
879  of RSA keys, this format would be 'securesystemslib.formats.RSAKEY_SCHEMA':
880
881  {'keytype': 'rsa',
882   'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...',
883              'private': '-----BEGIN RSA PRIVATE KEY----- ...'}}
884
885  'derived_key_information' is a dictionary of the form:
886    {'salt': '...',
887     'derived_key': '...',
888     'iterations': '...'}
889
890  'securesystemslib.exceptions.CryptoError' raised if the encryption fails.
891  """
892
893  # Generate a random Initialization Vector (IV).  Follow the provably secure
894  # encrypt-then-MAC approach, which affords the ability to verify ciphertext
895  # without needing to decrypt it and preventing an attacker from feeding the
896  # block cipher malicious data.  Modes like GCM provide both encryption and
897  # authentication, whereas CTR only provides encryption.
898
899  # Generate a random 128-bit IV.  Random bits of data is needed for salts and
900  # initialization vectors suitable for the encryption algorithms used in
901  # 'pyca_crypto_keys.py'.
902  iv = os.urandom(16)
903
904  # Construct an AES-CTR Cipher object with the given key and a randomly
905  # generated IV.
906  symmetric_key = derived_key_information['derived_key']
907  encryptor = Cipher(algorithms.AES(symmetric_key), modes.CTR(iv),
908      backend=default_backend()).encryptor()
909
910  # Encrypt the plaintext and get the associated ciphertext.
911  # Do we need to check for any exceptions?
912  ciphertext = encryptor.update(key_data.encode('utf-8')) + encryptor.finalize()
913
914  # Generate the hmac of the ciphertext to ensure it has not been modified.
915  # The decryption routine may verify a ciphertext without having to perform
916  # a decryption operation.
917  symmetric_key = derived_key_information['derived_key']
918  salt = derived_key_information['salt']
919  hmac_object = \
920    cryptography.hazmat.primitives.hmac.HMAC(symmetric_key, hashes.SHA256(),
921        backend=default_backend())
922  hmac_object.update(ciphertext)
923  hmac_value = binascii.hexlify(hmac_object.finalize())
924
925  # Store the number of PBKDF2 iterations used to derive the symmetric key so
926  # that the decryption routine can regenerate the symmetric key successfully.
927  # The PBKDF2 iterations are allowed to vary for the keys loaded and saved.
928  iterations = derived_key_information['iterations']
929
930  # Return the salt, iterations, hmac, initialization vector, and ciphertext
931  # as a single string.  These five values are delimited by
932  # '_ENCRYPTION_DELIMITER' to make extraction easier.  This delimiter is
933  # arbitrarily chosen and should not occur in the hexadecimal representations
934  # of the fields it is separating.
935  return binascii.hexlify(salt).decode() + _ENCRYPTION_DELIMITER + \
936      str(iterations) + _ENCRYPTION_DELIMITER + \
937      hmac_value.decode() + _ENCRYPTION_DELIMITER + \
938      binascii.hexlify(iv).decode() + _ENCRYPTION_DELIMITER + \
939      binascii.hexlify(ciphertext).decode()
940
941
942
943
944
945def _decrypt(file_contents, password):
946  """
947  The corresponding decryption routine for _encrypt().
948
949  'securesystemslib.exceptions.CryptoError' raised if the decryption fails.
950  """
951
952  # Extract the salt, iterations, hmac, initialization vector, and ciphertext
953  # from 'file_contents'.  These five values are delimited by
954  # '_ENCRYPTION_DELIMITER'.  This delimiter is arbitrarily chosen and should
955  # not occur in the hexadecimal representations of the fields it is
956  # separating.  Raise 'securesystemslib.exceptions.CryptoError', if
957  # 'file_contents' does not contains the expected data layout.
958  try:
959    salt, iterations, hmac, iv, ciphertext = \
960      file_contents.split(_ENCRYPTION_DELIMITER)
961
962  except ValueError:
963    raise securesystemslib.exceptions.CryptoError('Invalid encrypted file.')
964
965  # Ensure we have the expected raw data for the delimited cryptographic data.
966  salt = binascii.unhexlify(salt.encode('utf-8'))
967  iterations = int(iterations)
968  iv = binascii.unhexlify(iv.encode('utf-8'))
969  ciphertext = binascii.unhexlify(ciphertext.encode('utf-8'))
970
971  # Generate derived key from 'password'.  The salt and iterations are
972  # specified so that the expected derived key is regenerated correctly.
973  # Discard the old "salt" and "iterations" values, as we only need the old
974  # derived key.
975  junk_old_salt, junk_old_iterations, symmetric_key = \
976    _generate_derived_key(password, salt, iterations)
977
978  # Verify the hmac to ensure the ciphertext is valid and has not been altered.
979  # See the encryption routine for why we use the encrypt-then-MAC approach.
980  # The decryption routine may verify a ciphertext without having to perform
981  # a decryption operation.
982  generated_hmac_object = \
983    cryptography.hazmat.primitives.hmac.HMAC(symmetric_key, hashes.SHA256(),
984        backend=default_backend())
985  generated_hmac_object.update(ciphertext)
986  generated_hmac = binascii.hexlify(generated_hmac_object.finalize())
987
988
989  if not securesystemslib.util.digests_are_equal(generated_hmac.decode(), hmac):
990    raise securesystemslib.exceptions.CryptoError('Decryption failed.')
991
992  # Construct a Cipher object, with the key and iv.
993  decryptor = Cipher(algorithms.AES(symmetric_key), modes.CTR(iv),
994      backend=default_backend()).decryptor()
995
996  # Decryption gets us the authenticated plaintext.
997  plaintext = decryptor.update(ciphertext) + decryptor.finalize()
998
999  return plaintext
1000
1001
1002
1003
1004
1005if __name__ == '__main__':
1006  # The interactive sessions of the documentation strings can be tested by
1007  # running 'pyca_crypto_keys.py' as a standalone module:
1008  # $ python pyca_crypto_keys.py
1009  import doctest
1010  doctest.testmod()
1011