diff options
author | Lianhao Lu <lianhao.lu@intel.com> | 2018-08-27 17:11:10 +0800 |
---|---|---|
committer | Lianhao Lu <lianhao.lu@intel.com> | 2018-08-27 17:11:10 +0800 |
commit | 0efadc1f20a3b578da1e54f2b4284099a4c826dd (patch) | |
tree | 1e5967abb260e06676aa37ebdf3af8760e069e2e /vnfsdk_pkgtools | |
parent | a570b0bbe05295b469c975916faf19919c656cd2 (diff) |
Support signing and certificate
Added the support of certificate and signing based on SOL-004.
Change-Id: I864f298edbcd85a9da2126d369a5b98d7950d590
Issue-ID: VNFSDK-144
Signed-off-by: Lianhao Lu <lianhao.lu@intel.com>
Diffstat (limited to 'vnfsdk_pkgtools')
-rw-r--r-- | vnfsdk_pkgtools/cli/__main__.py | 18 | ||||
-rw-r--r-- | vnfsdk_pkgtools/packager/csar.py | 58 | ||||
-rw-r--r-- | vnfsdk_pkgtools/packager/manifest.py | 32 |
3 files changed, 97 insertions, 11 deletions
diff --git a/vnfsdk_pkgtools/cli/__main__.py b/vnfsdk_pkgtools/cli/__main__.py index 23dbe02..2b262b9 100644 --- a/vnfsdk_pkgtools/cli/__main__.py +++ b/vnfsdk_pkgtools/cli/__main__.py @@ -25,6 +25,7 @@ import tempfile from vnfsdk_pkgtools import validator def csar_create_func(namespace): + csar.write(namespace.source, namespace.entry, namespace.destination, @@ -32,14 +33,16 @@ def csar_create_func(namespace): def csar_open_func(namespace): csar.read(namespace.source, - namespace.destination) + namespace.destination, + namespace.no_verify_cert) def csar_validate_func(namespace): workdir = tempfile.mkdtemp() try: reader = None reader = csar.read(namespace.source, - workdir) + workdir, + no_verify_cert=True) driver = validator.get_validator(namespace.parser) driver.validate(reader) @@ -87,6 +90,12 @@ def parse_args(args_list): '--digest', choices=['SHA256', 'SHA512'], help='If present, means to check the file deigest in manifest; compute the digest using the specified hash algorithm of all files in the csar package to be put into the manifest file') + csar_create.add_argument( + '--certificate', + help='Certificate file for certification, relative to service template directory') + csar_create.add_argument( + '--privkey', + help='Private key file for certification, absoluate or relative path') csar_open = subparsers.add_parser('csar-open') @@ -98,6 +107,11 @@ def parse_args(args_list): '-d', '--destination', help='Output directory to extract the CSAR into', required=True) + csar_open.add_argument( + '--no-verify-cert', + action='store_true', + help="Do NOT verify the signer's certificate") + csar_validate = subparsers.add_parser('csar-validate') csar_validate.set_defaults(func=csar_validate_func) diff --git a/vnfsdk_pkgtools/packager/csar.py b/vnfsdk_pkgtools/packager/csar.py index 162985f..a397f2e 100644 --- a/vnfsdk_pkgtools/packager/csar.py +++ b/vnfsdk_pkgtools/packager/csar.py @@ -23,6 +23,7 @@ import requests from ruamel import yaml # @UnresolvedImport from vnfsdk_pkgtools.packager import manifest +from vnfsdk_pkgtools.packager import utils LOG = logging.getLogger(__name__) @@ -38,6 +39,7 @@ META_ENTRY_MANIFEST_FILE_KEY = 'Entry-Manifest' META_ENTRY_HISTORY_FILE_KEY = 'Entry-Change-Log' META_ENTRY_TESTS_DIR_KEY = 'Entry-Tests' META_ENTRY_LICENSES_DIR_KEY = 'Entry-Licenses' +META_ENTRY_CERT_FILE_KEY = 'Entry-Certificate' BASE_METADATA = { META_FILE_VERSION_KEY: META_FILE_VERSION_VALUE, @@ -108,6 +110,19 @@ def write(source, entry, destination, args): check_dir=False) metadata[META_ENTRY_HISTORY_FILE_KEY] = args.history + if args.certificate: + check_file_dir(root=source, + entry=args.certificate, + msg='Please specify a valid certificate file.', + check_dir=False) + metadata[META_ENTRY_CERT_FILE_KEY] = args.certificate + if not args.privkey: + raise RuntimeError('Need private key file for signing') + check_file_dir(root='', + entry=args.privkey, + msg='Please specify a valid private key file.', + check_dir=False) + if(args.tests): check_file_dir(root=source, entry=args.tests, @@ -144,8 +159,14 @@ def write(source, entry, destination, args): f.write(dir_full_path + os.sep, dir_relative_path) if manifest_file: - if args.digest: - LOG.debug('Update manifest file to temporary file') + LOG.debug('Update manifest file to temporary file') + manifest_file_full_path = manifest_file.update_to_file(True) + if args.certificate and args.privkey: + LOG.debug('calculate signature') + manifest_file.signature = utils.sign(msg_file=manifest_file_full_path, + cert_file=os.path.join(source, args.certificate), + key_file=args.privkey) + # write cms into it manifest_file_full_path = manifest_file.update_to_file(True) LOG.debug('Writing to archive: {0}'.format(args.manifest)) f.write(manifest_file_full_path, args.manifest) @@ -156,7 +177,7 @@ def write(source, entry, destination, args): class _CSARReader(object): - def __init__(self, source, destination): + def __init__(self, source, destination, no_verify_cert=True): if os.path.isdir(destination) and os.listdir(destination): raise ValueError('{0} already exists and is not empty. ' 'Please specify the location where the CSAR ' @@ -179,7 +200,7 @@ class _CSARReader(object): raise ValueError('{0} is not a valid CSAR.'.format(self.source)) self._extract() self._read_metadata() - self._validate() + self._validate(no_verify_cert) finally: if downloaded_csar: os.remove(self.source) @@ -221,6 +242,10 @@ class _CSARReader(object): def entry_licenses_dir(self): return self.metadata.get(META_ENTRY_LICENSES_DIR_KEY) + @property + def entry_certificate_file(self): + return self.metadata.get(META_ENTRY_CERT_FILE_KEY) + def _extract(self): LOG.debug('Extracting CSAR contents') if not os.path.exists(self.destination): @@ -239,7 +264,7 @@ class _CSARReader(object): self.metadata.update(yaml.load(f)) LOG.debug('CSAR metadata:\n{0}'.format(pprint.pformat(self.metadata))) - def _validate(self): + def _validate(self, no_verify_cert): def validate_key(key, expected=None): if not self.metadata.get(key): raise ValueError('{0} is missing from the metadata file.'.format(key)) @@ -256,6 +281,7 @@ class _CSARReader(object): LOG.debug('CSAR change history file: {0}'.format(self.entry_history_file)) LOG.debug('CSAR tests directory: {0}'.format(self.entry_tests_dir)) LOG.debug('CSAR licenses directory: {0}'.format(self.entry_licenses_dir)) + LOG.debug('CSAR certificate file: {0}'.format(self.entry_certificate_file)) check_file_dir(self.destination, self.entry_definitions, @@ -294,6 +320,22 @@ class _CSARReader(object): 'file does not exist.'.format(self.entry_licenses_dir), check_dir=True) + if(self.entry_certificate_file): + # check certificate + check_file_dir(self.destination, + self.entry_certificate_file, + 'The certificate file {0} referenced by the metadata ' + 'file does not exist.'.format(self.entry_certificate_file), + check_dir=False) + tmp_manifest = self.manifest.save_to_temp_without_cms() + utils.verify(tmp_manifest, + os.path.join(self.destination, self.entry_certificate_file), + self.manifest.signature, + no_verify_cert) + os.unlink(tmp_manifest) + + + def _download(self, url, target): response = requests.get(url, stream=True) if response.status_code != 200: @@ -306,5 +348,7 @@ class _CSARReader(object): f.write(chunk) -def read(source, destination): - return _CSARReader(source=source, destination=destination) +def read(source, destination, no_verify_cert=False): + return _CSARReader(source=source, + destination=destination, + no_verify_cert=no_verify_cert) diff --git a/vnfsdk_pkgtools/packager/manifest.py b/vnfsdk_pkgtools/packager/manifest.py index e5bceb0..937f14e 100644 --- a/vnfsdk_pkgtools/packager/manifest.py +++ b/vnfsdk_pkgtools/packager/manifest.py @@ -123,9 +123,12 @@ class Manifest(object): self.digests[desc['Source']] = (desc['Algorithm'], desc['Hash']) elif key: raise ManifestException("Unknown key in line '%s:%s'" % (key, value)) + elif '--BEGIN CMS--' in remain: + if '--END CMS--' not in block[-1]: + raise ManifestException("Can NOT find end of sigature block") + self.signature = remain + '\n' + '\n'.join(block) else: - # TODO signature block - pass + raise ManifestException("Unknown content: '%s'" % remain) if not self.metadata: raise ManifestException("No metadata") @@ -155,6 +158,10 @@ class Manifest(object): ret += "Source: %s\n" % key ret += "Algorithm: %s\n" % digest[0] ret += "Hash: %s\n" % digest[1] + # signature + if self.signature: + ret += "\n" + ret += self.signature return ret def update_to_file(self, temporary=False): @@ -167,3 +174,24 @@ class Manifest(object): with open(abs_path, 'w') as fp: fp.write(content) return abs_path + + def save_to_temp_without_cms(self): + # we need to strip cms block with out changing the order of the + # file digest content before we verify the signature + tmpfile = tempfile.NamedTemporaryFile(delete=False) + skip = False + lines = [] + with open(os.path.join(self.root, self.path),'rU') as fp: + for line in fp: + if '--BEGIN CMS--' in line: + skip = True + elif '--END CMS--' in line: + skip = False + elif not skip: + lines.append(line) + # strip trailing empty lines + content = ''.join(lines).rstrip(' \n\t') + content += '\n' + tmpfile.write(content) + tmpfile.close() + return tmpfile.name |