From 53ff336468edf1212f6495333696b48b9ae2fdea Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Thu, 17 Jul 2014 13:37:31 -0500 Subject: [PATCH 1/3] Clean up pem keys A change of line or any other character outside the public key makes changes the keyid and breaks the behaviour of TUF. Remove everything before '-----BEGIN PUBLIC KEY-----' and after '-----END PUBLIC KEY-----'. --- tuf/keys.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tuf/keys.py b/tuf/keys.py index c3af4d6638..87235288c6 100755 --- a/tuf/keys.py +++ b/tuf/keys.py @@ -63,6 +63,9 @@ # http://docs.python.org/2/library/warnings.html#temporarily-suppressing-warnings import warnings +# Used by the PEM key import code to clean up the key +import re + # 'pycrypto' is the only currently supported library for the creation of RSA # keys. # https://github.com/dlitz/pycrypto @@ -1010,11 +1013,16 @@ def format_rsakey_from_pem(pem): # Raise 'tuf.FormatError' if the check fails. tuf.formats.PEMRSA_SCHEMA.check_match(pem) - # Ensure the PEM string starts with the required number of dashes. Although - # a simple validation of 'pem' is performed here, a fully valid PEM string is - # needed to successfully verify signatures. - if not pem.startswith('-----'): + # Ensure the PEM string has a valid header and footer + pem_header = '-----BEGIN PUBLIC KEY-----' + pem_footer = '-----END PUBLIC KEY-----' + if pem_header not in pem or pem_footer not in pem: raise tuf.FormatError('The PEM string argument is improperly formatted.') + + # Clean up the PEM string. Remove everything in the string before the header + # and after the footer. + pem = re.sub("(.+)" + pem_header, pem_header, pem, flags=re.S|re.M) + pem = re.sub(pem_footer + "(.+)", pem_footer, pem, flags=re.S|re.M) # Begin building the RSA key dictionary. rsakey_dict = {} From 95ab4e3172be2848d220cdfbaaac407294571a1c Mon Sep 17 00:00:00 2001 From: vladdd Date: Fri, 18 Jul 2014 15:19:04 -0400 Subject: [PATCH 2/3] Replace re.sub() with string.index() re.sub() with the 'flags' keyword argument caused type error in Python 2.6. Converted PEM validation to use index() to address issue above. Raise specific exception depending on missing header / footer. Check footer follows header. --- tuf/keys.py | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/tuf/keys.py b/tuf/keys.py index 87235288c6..acc44247a7 100755 --- a/tuf/keys.py +++ b/tuf/keys.py @@ -63,9 +63,6 @@ # http://docs.python.org/2/library/warnings.html#temporarily-suppressing-warnings import warnings -# Used by the PEM key import code to clean up the key -import re - # 'pycrypto' is the only currently supported library for the creation of RSA # keys. # https://github.com/dlitz/pycrypto @@ -980,7 +977,7 @@ def format_rsakey_from_pem(pem): {'keytype': 'rsa', 'keyid': keyid, - 'keyval': {'public': '-----BEGIN RSA PUBLIC KEY----- ...', + 'keyval': {'public': '-----BEGIN PUBLIC KEY----- ...', 'private': ''}} The public portion of the RSA key is a string in PEM format. @@ -1000,7 +997,9 @@ def format_rsakey_from_pem(pem): tuf.FormatError, if 'pem' is improperly formatted. - None. + Only the public portion of the PEM is extracted. Leading or trailing + whitespace is not included in the PEM string stored in the rsakey object + returned. A dictionary containing the RSA keys and other identifying information. @@ -1013,21 +1012,43 @@ def format_rsakey_from_pem(pem): # Raise 'tuf.FormatError' if the check fails. tuf.formats.PEMRSA_SCHEMA.check_match(pem) - # Ensure the PEM string has a valid header and footer + # Ensure the PEM string has a valid header and footer. Although a simple + # validation of 'pem' is performed here, a fully valid PEM string is + # needed to later successfully verify signatures. pem_header = '-----BEGIN PUBLIC KEY-----' pem_footer = '-----END PUBLIC KEY-----' - if pem_header not in pem or pem_footer not in pem: - raise tuf.FormatError('The PEM string argument is improperly formatted.') + header_start = 0 + footer_start = 0 - # Clean up the PEM string. Remove everything in the string before the header - # and after the footer. - pem = re.sub("(.+)" + pem_header, pem_header, pem, flags=re.S|re.M) - pem = re.sub(pem_footer + "(.+)", pem_footer, pem, flags=re.S|re.M) + # Raise error message if the expected header or footer is not found in 'pem'. + try: + header_start = pem.index(pem_header) + + except ValueError: + message = \ + 'Required PEM header ' + repr(pem_header) + '\n not found in PEM' + \ + ' string: ' + repr(pem) + raise tuf.FormatError(message) + try: + # Search for 'pem_footer' after the PEM header. + footer_start = pem.index(pem_footer, header_start + len(pem_header)) + + except ValueError: + message = \ + 'Required PEM footer ' + repr(pem_footer) + '\n not found in PEM' + \ + ' string ' + repr(pem) + raise tuf.FormatError(message) + + # Extract only the public portion of 'pem'. Leading or trailing whitespace + # is not included. + public_pem = pem[header_start:footer_start + len(pem_footer)] + + # Begin building the RSA key dictionary. rsakey_dict = {} keytype = 'rsa' - public = pem + public = public_pem # Generate the keyid of the RSA key. 'key_value' corresponds to the # 'keyval' entry of the 'RSAKEY_SCHEMA' dictionary. The private key From 3fd6839c2b6407b916b0018352343b29e77de6a8 Mon Sep 17 00:00:00 2001 From: vladdd Date: Fri, 18 Jul 2014 15:24:10 -0400 Subject: [PATCH 3/3] Add test case for keys.formats_rsakey_from_pem() Add test_formats_rsakey_from_pem() test case and test conditions for improperly formatted PEM. Add test condition to verify trailing whitespace is removed and matches the expected PEM format generated internally. Public PEMs generated externally (with the expected trailing newline) was not previously considered. --- tests/test_keys.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_keys.py b/tests/test_keys.py index 5e8b1c12f8..e4a6cb0e11 100755 --- a/tests/test_keys.py +++ b/tests/test_keys.py @@ -98,6 +98,31 @@ def test_format_keyval_to_metadata(self): self.assertRaises(tuf.FormatError, KEYS.format_keyval_to_metadata, keytype, keyvalue) keyvalue['public'] = public + + + + def test_format_rsakey_from_pem(self): + pem = self.rsakey_dict['keyval']['public'] + rsa_key = KEYS.format_rsakey_from_pem(pem) + + # Check if the format of the object returned by this function corresponds + # to 'tuf.formats.RSAKEY_SCHEMA' format. + self.assertTrue(tuf.formats.RSAKEY_SCHEMA.matches(rsa_key)) + + # Verify whitespace is stripped. + self.assertEqual(rsa_key, KEYS.format_rsakey_from_pem(pem + '\n')) + + # Supplying a 'bad_pem' argument. + self.assertRaises(tuf.FormatError, KEYS.format_rsakey_from_pem, 'bad_pem') + + # Supplying an improperly formatted PEM. + # Strip the PEM header and footer. + pem_header = '-----BEGIN PUBLIC KEY-----' + pem_footer= '-----END PUBLIC KEY-----' + self.assertRaises(tuf.FormatError, KEYS.format_rsakey_from_pem, + pem[:len(pem_footer)]) + self.assertRaises(tuf.FormatError, KEYS.format_rsakey_from_pem, + pem[len(pem_header):])