123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- # Copyright (C) 2012 Internet Systems Consortium.
- #
- # Permission to use, copy, modify, and distribute this software for any
- # purpose with or without fee is hereby granted, provided that the above
- # copyright notice and this permission notice appear in all copies.
- #
- # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
- # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
- # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
- # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
- # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
- # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
- # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- # Note: the main code is in C++, but what we are mostly testing is
- # options and behaviour (output/file creation, etc), which is easier
- # to test in python.
- import unittest
- import os
- from subprocess import call
- import subprocess
- import ssl
- import stat
- def run(command):
- """
- Small helper function that returns a tuple of (rcode, stdout, stderr) after
- running the given command (an array of command and arguments, as passed on
- to subprocess).
- """
- subp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- (stdout, stderr) = subp.communicate()
- return (subp.returncode, stdout, stderr)
- class FileDeleterContext:
- """
- Simple Context Manager that deletes a given set of files when the context
- is left.
- """
- def __init__(self, files):
- self.files = files
- def __enter__(self):
- pass
- def __exit__(self, type, value, traceback):
- for f in self.files:
- if os.path.exists(f):
- os.unlink(f)
- class FilePermissionContext:
- """
- Simple Context Manager that temporarily modifies file permissions for
- a given file
- """
- def __init__(self, f, unset_flags = [], set_flags = []):
- """
- Initialize file permission context.
- See the stat module for possible flags to set or unset.
- The flags are changed when the context is entered (i.e.
- you can create the context first without any change)
- The flags are changed back when the context is left.
- Parameters:
- f: string, file to change permissions for
- unset_flags: list of flags to unset
- set_flags: list of flags to set
- """
- self.file = f
- self.orig_mode = os.stat(f).st_mode
- new_mode = self.orig_mode
- for flag in unset_flags:
- new_mode = new_mode & ~flag
- for flag in set_flags:
- new_mode = new_mode | flag
- self.new_mode = new_mode
- def __enter__(self):
- os.chmod(self.file, self.new_mode)
- def __exit__(self, type, value, traceback):
- os.chmod(self.file, self.orig_mode)
- def read_file_data(filename):
- """
- Simple text file reader that returns its contents as an array
- """
- with open(filename) as f:
- return f.readlines()
- class TestCertGenTool(unittest.TestCase):
- TOOL = '../b10-certgen'
- def run_check(self, expected_returncode, expected_stdout, expected_stderr, command):
- """
- Runs the given command, and checks return code, and outputs (if provided).
- Arguments:
- expected_returncode, return code of the command
- expected_stdout, (multiline) string that is checked agains stdout.
- May be None, in which case the check is skipped.
- expected_stderr, (multiline) string that is checked agains stderr.
- May be None, in which case the check is skipped.
- """
- (returncode, stdout, stderr) = run(command)
- self.assertEqual(expected_returncode, returncode, " ".join(command))
- if expected_stdout is not None:
- self.assertEqual(expected_stdout, stdout.decode())
- if expected_stderr is not None:
- self.assertEqual(expected_stderr, stderr.decode())
- def validate_certificate(self, expected_result, certfile):
- """
- Validate a certificate, using the quiet option of the tool; it runs
- the check option (-c) for the given base name of the certificate (-f
- <certfile>), and compares the return code to the given
- expected_result value
- """
- self.run_check(expected_result, '', '',
- [self.TOOL, '-q', '-c', certfile])
- # Same with long options
- self.run_check(expected_result, '', '',
- [self.TOOL, '--quiet', '--certfile', certfile])
- def test_basic_creation(self):
- """
- Tests whether basic creation with no arguments (except output
- file name) successfully creates a key and certificate
- """
- keyfile = 'test-keyfile.pem'
- certfile = 'test-certfile.pem'
- command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ]
- self.creation_helper(command, certfile, keyfile)
- # Do same with long options
- command = [ self.TOOL, '--quiet', '--write', '--certfile=' + certfile, '--keyfile=' + keyfile ]
- self.creation_helper(command, certfile, keyfile)
- def creation_helper(self, command, certfile, keyfile):
- """
- Helper method for test_basic_creation.
- Performs the actual checks
- """
- with FileDeleterContext([keyfile, certfile]):
- self.assertFalse(os.path.exists(keyfile))
- self.assertFalse(os.path.exists(certfile))
- self.run_check(0, '', '', command)
- self.assertTrue(os.path.exists(keyfile))
- self.assertTrue(os.path.exists(certfile))
- # Validate the certificate that was just created
- self.validate_certificate(0, certfile)
- # When run with the same options, it should *not* create it again,
- # as the current certificate should still be valid
- certdata = read_file_data(certfile)
- keydata = read_file_data(keyfile)
- self.run_check(0, '', '', command)
- self.assertEqual(certdata, read_file_data(certfile))
- self.assertEqual(keydata, read_file_data(keyfile))
- # but if we add -f, it should force a new creation
- command.append('-f')
- self.run_check(0, '', '', command)
- self.assertNotEqual(certdata, read_file_data(certfile))
- self.assertNotEqual(keydata, read_file_data(keyfile))
- def test_check_bad_certificates(self):
- """
- Tests a few pre-created certificates with the -c option
- """
- path = os.environ['CMDCTL_SRC_PATH'] + '/tests/testdata/'
- self.validate_certificate(10, path + 'expired-certfile.pem')
- self.validate_certificate(100, path + 'mangled-certfile.pem')
- self.validate_certificate(17, path + 'noca-certfile.pem')
- def test_bad_options(self):
- """
- Tests some combinations of commands that should fail.
- """
- # specify -c but not -k
- self.run_check(101,
- 'Error: keyfile and certfile must both be specified '
- 'if one of them is when calling b10-certgen in write '
- 'mode.\n',
- '', [self.TOOL, '-w', '-c', 'foo'])
- self.run_check(101,
- 'Error: keyfile and certfile must both be specified '
- 'if one of them is when calling b10-certgen in write '
- 'mode.\n',
- '', [self.TOOL, '-w', '-k', 'foo'])
- self.run_check(101,
- 'Error: keyfile is not used when not in write mode\n',
- '', [self.TOOL, '-k', 'foo'])
- # Extraneous argument
- self.run_check(101, None, None, [self.TOOL, 'foo'])
- # No such file
- self.run_check(105, None, None, [self.TOOL, '-c', 'foo'])
- @unittest.skipIf(os.getuid() == 0,
- 'test cannot be run as root user')
- def test_permissions(self):
- """
- Test some combinations of correct and bad permissions.
- """
- keyfile = 'mod-keyfile.pem'
- certfile = 'mod-certfile.pem'
- command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ]
- # Delete them at the end
- with FileDeleterContext([keyfile, certfile]):
- # Create the two files first
- self.run_check(0, '', '', command)
- self.validate_certificate(0, certfile)
- # Make the key file unwritable
- with FilePermissionContext(keyfile, unset_flags = [stat.S_IWUSR]):
- self.run_check(106, '', '', command)
- # Should have no effect on validation
- self.validate_certificate(0, certfile)
- # Make the cert file unwritable
- with FilePermissionContext(certfile, unset_flags = [stat.S_IWUSR]):
- self.run_check(106, '', '', command)
- # Should have no effect on validation
- self.validate_certificate(0, certfile)
- # Make the key file unreadable (this should not matter)
- with FilePermissionContext(keyfile, unset_flags = [stat.S_IRUSR]):
- self.run_check(0, '', '', command)
- # unreadable key file should also not have any effect on
- # validation
- self.validate_certificate(0, certfile)
- # Make the cert file unreadable (this should matter)
- with FilePermissionContext(certfile, unset_flags = [stat.S_IRUSR]):
- self.run_check(106, '', '', command)
- # Unreadable cert file should also fail validation
- self.validate_certificate(106, certfile)
- # Not directly a permission problem, but trying to check or create
- # in a nonexistent directory returns different error codes
- self.validate_certificate(105, 'fakedir/cert')
- self.run_check(103, '', '', [ self.TOOL, '-q', '-w', '-c',
- 'fakedir/cert', '-k', 'fakedir/key' ])
- if __name__== '__main__':
- unittest.main()
|