Skip to content

Commit

Permalink
Merge pull request #60 from vladimir-v-diaz/lukpueh-pem-format-quickfix
Browse files Browse the repository at this point in the history
Review of PEM format quickfix #54
  • Loading branch information
vladimir-v-diaz authored Sep 19, 2017
2 parents 38d82de + 8d13300 commit 5142931
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 33 deletions.
51 changes: 23 additions & 28 deletions securesystemslib/pyca_crypto_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,11 +473,13 @@ def verify_rsa_signature(signature, signature_scheme, public_key, data):
def create_rsa_encrypted_pem(private_key, passphrase):
"""
<Purpose>
Return a string in PEM format, where the private part of the RSA key is
encrypted. The private part of the RSA key is encrypted by the Triple
Data Encryption Algorithm (3DES) and Cipher-block chaining (CBC) for the
mode of operation. Password-Based Key Derivation Function 1 (PBKF1) + MD5
is used to strengthen 'passphrase'.
Return a string in PEM format (TraditionalOpenSSL), where the
private part of the RSA key is encrypted using the best available
encryption for a given key's backend. This is a curated encryption choice
and the algorithm may change over time.
c.f. cryptography.io/en/latest/hazmat/primitives/asymmetric/serialization/
#cryptography.hazmat.primitives.serialization.BestAvailableEncryption
>>> public, private = generate_rsa_public_and_private(2048)
>>> passphrase = 'secret'
Expand All @@ -491,26 +493,23 @@ def create_rsa_encrypted_pem(private_key, passphrase):
passphrase:
The passphrase, or password, to encrypt the private part of the RSA
key. 'passphrase' is not used directly as the encryption key, a stronger
encryption key is derived from it.
key.
<Exceptions>
securesystemslib.exceptions.FormatError, if the arguments are improperly formatted.
securesystemslib.exceptions.FormatError, if the arguments are improperly
formatted.
securesystemslib.exceptions.CryptoError, if an RSA key in encrypted PEM format cannot be created.
securesystemslib.exceptions.CryptoError, if the passed RSA key cannot be
deserialized by pyca cryptography.
ValueError, if 'private_key' is unset.
<Side Effects>
PyCrypto's Crypto.PublicKey.RSA.exportKey() called to perform the actual
generation of the PEM-formatted output.
<Returns>
A string in PEM format, where the private RSA key is encrypted.
Conforms to 'securesystemslib.formats.PEMRSA_SCHEMA'.
A string in PEM format (TraditionalOpenSSL), where the private RSA key is
encrypted. Conforms to 'securesystemslib.formats.PEMRSA_SCHEMA'.
"""

# Does 'private_key' have the correct format?
# This check will ensure 'private_key' has the appropriate number
# of objects and object types, and that all dict keys are properly named.
# Raise 'securesystemslib.exceptions.FormatError' if the check fails.
Expand All @@ -519,29 +518,25 @@ def create_rsa_encrypted_pem(private_key, passphrase):
# Does 'passphrase' have the correct format?
securesystemslib.formats.PASSWORD_SCHEMA.check_match(passphrase)

# 'private_key' is in PEM format and unencrypted. The extracted key will be
# imported and converted to PyCrypto's RSA key object (i.e.,
# Crypto.PublicKey.RSA). Use PyCrypto's exportKey method, with a passphrase
# specified, to create the string. PyCrypto uses PBKDF1+MD5 to strengthen
# 'passphrase', and 3DES with CBC mode for encryption. 'private_key' may
# still be a NULL string after the 'securesystemslib.formats.PEMRSA_SCHEMA'
# (i.e., 'private_key' has variable size and can be an empty string.
# 'private_key' may still be a NULL string after the
# 'securesystemslib.formats.PEMRSA_SCHEMA' so we need an additional check
if len(private_key):
try:
private_key = load_pem_private_key(private_key.encode('utf-8'),
password=None,
backend=default_backend())
except ValueError:
raise securesystemslib.exceptions.CryptoError('The private key (in PEM format) could not be'
' deserialized.')
raise securesystemslib.exceptions.CryptoError('The private key'
' (in PEM format) could not be deserialized.')

else:
raise ValueError('The required private key is unset.')

encrypted_pem = \
private_key.private_bytes(encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(passphrase.encode('utf-8')))
encrypted_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.BestAvailableEncryption(
passphrase.encode('utf-8')))

return encrypted_pem.decode()

Expand Down
21 changes: 16 additions & 5 deletions tests/test_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ def test_create_rsa_encrypted_pem(self):
scheme = 'rsassa-pss-sha256'
encrypted_pem = KEYS.create_rsa_encrypted_pem(private, passphrase)
self.assertTrue(securesystemslib.formats.PEMRSA_SCHEMA.matches(encrypted_pem))
self.assertTrue(KEYS.is_pem_private(encrypted_pem))

# Try to import the encrypted PEM file.
rsakey = KEYS.import_rsakey_from_private_pem(encrypted_pem, scheme, passphrase)
Expand Down Expand Up @@ -690,6 +691,13 @@ def test_extract_pem(self):
private_pem=False)
self.assertTrue(securesystemslib.formats.PEMRSA_SCHEMA.matches(public_pem))

# Test encrypted private pem
encrypted_private_pem = KEYS.create_rsa_encrypted_pem(private_pem, "pw")
encrypted_private_pem_stripped = KEYS.extract_pem(encrypted_private_pem,
private_pem=True)
self.assertTrue(securesystemslib.formats.PEMRSA_SCHEMA.matches(
encrypted_private_pem_stripped))

# Test for an invalid PEM.
pem_header = '-----BEGIN RSA PRIVATE KEY-----'
pem_footer = '-----END RSA PRIVATE KEY-----'
Expand Down Expand Up @@ -731,7 +739,7 @@ def test_is_pem_public(self):
public_pem = self.rsakey_dict['keyval']['public']
self.assertTrue(KEYS.is_pem_public(public_pem))

# Tesst for a valid non-public PEM string.
# Test for a valid non-public PEM string.
private_pem = self.rsakey_dict['keyval']['private']
self.assertFalse(KEYS.is_pem_public(private_pem))

Expand All @@ -743,11 +751,14 @@ def test_is_pem_public(self):

def test_is_pem_private(self):
# Test for a valid PEM string.
private_pem = self.rsakey_dict['keyval']['private']
private_pem_rsa = self.rsakey_dict['keyval']['private']
private_pem_ec = self.ecdsakey_dict['keyval']['private']
encrypted_private_pem_rsa = KEYS.create_rsa_encrypted_pem(
private_pem_rsa, "pw")

self.assertTrue(KEYS.is_pem_private(private_pem))
self.assertTrue(KEYS.is_pem_private(private_pem_rsa))
self.assertTrue(KEYS.is_pem_private(private_pem_ec, 'ec'))
self.assertTrue(KEYS.is_pem_private(encrypted_private_pem_rsa))

# Test for a valid non-private PEM string.
public_pem = self.rsakey_dict['keyval']['public']
Expand All @@ -757,11 +768,11 @@ def test_is_pem_private(self):

# Test for unsupported keytype.
self.assertRaises(securesystemslib.exceptions.FormatError,
KEYS.is_pem_private, private_pem, 'bad_keytype')
KEYS.is_pem_private, private_pem_rsa, 'bad_keytype')

# Test for an invalid PEM string.
self.assertRaises(securesystemslib.exceptions.FormatError,
KEYS.is_pem_private, 123)
KEYS.is_pem_private, 123)



Expand Down

0 comments on commit 5142931

Please sign in to comment.