Tryag File Manager
Home
-
Turbo Force
Current Path :
/
usr
/
lib
/
python2.4
/
site-packages
/
rhsm
/
Upload File :
New :
File
Dir
//usr/lib/python2.4/site-packages/rhsm/certificate2.py
# # Copyright (c) 2012 Red Hat, Inc. # # This software is licensed to you under the GNU General Public License, # version 2 (GPLv2). There is NO WARRANTY for this software, express or # implied, including the implied warranties of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 # along with this software; if not, see # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. # # Red Hat trademarks are not licensed under GPLv2. No permission is # granted to use or replicate Red Hat trademarks that are incorporated # in this software or its documentation. # import base64 import logging import os import posixpath import re import zlib log = logging.getLogger(__name__) from datetime import datetime, timedelta from rhsm import _certificate from rhsm.connection import safe_int from rhsm.certificate import Extensions, OID, DateRange, GMT, \ get_datetime_from_x509, parse_tags, CertificateException from rhsm.pathtree import PathTree from rhsm import ourjson as json REDHAT_OID_NAMESPACE = "1.3.6.1.4.1.2312.9" ORDER_NAMESPACE = "4" EXT_ORDER_NAME = "4.1" EXT_CERT_VERSION = "6" EXT_ENT_PAYLOAD = "7" # Constants representing the type of certificates: PRODUCT_CERT = 1 ENTITLEMENT_CERT = 2 IDENTITY_CERT = 3 class _CertFactory(object): """ Factory for creating certificate objects. Examines the incoming file or PEM text, parses the OID structure, from the server, and returns the correct implementation class. determines the type of certificate we're dealing with (entitlement/product), as well as the version of the certificate NOTE: Please use the factory methods that leverage this class in certificate.py instead of this class. """ def create_from_file(self, path): """ Create appropriate certificate object from a PEM file on disk. """ pem = open(path, 'r').read() return self._read_x509(_certificate.load(path), path, pem) def create_from_pem(self, pem, path=None): """ Create appropriate certificate object from a PEM string. """ if not pem: raise CertificateException("Empty certificate") return self._read_x509(_certificate.load(pem=pem), path, pem) def _read_x509(self, x509, path, pem): if not x509: raise CertificateException("Error loading certificate") # Load the X509 extensions so we can determine what we're dealing with: try: extensions = _Extensions2(x509) redhat_oid = OID(REDHAT_OID_NAMESPACE) # Trim down to only the extensions in the Red Hat namespace: extensions = extensions.ltrim(len(redhat_oid)) # Check the certificate version, absence of the extension implies v1.0: cert_version_str = "1.0" if EXT_CERT_VERSION in extensions: cert_version_str = extensions[EXT_CERT_VERSION] version = Version(cert_version_str) if version.major == 1: return self._create_v1_cert(version, extensions, x509, path) if version.major == 3: return self._create_v3_cert(version, extensions, x509, path, pem) except CertificateException, e: raise e except Exception, e: log.exception(e) raise CertificateException(str(e)) def _create_v1_cert(self, version, extensions, x509, path): cert_type = self._get_v1_cert_type(extensions) if cert_type == IDENTITY_CERT: return self._create_identity_cert(version, extensions, x509, path) elif cert_type == ENTITLEMENT_CERT: return self._create_v1_ent_cert(version, extensions, x509, path) elif cert_type == PRODUCT_CERT: return self._create_v1_prod_cert(version, extensions, x509, path) def _read_alt_name(self, x509): return x509.get_extension(name='subjectAltName') def _read_issuer(self, x509): return x509.get_issuer() def _read_subject(self, x509): return x509.get_subject() def _create_identity_cert(self, version, extensions, x509, path): cert = IdentityCertificate( x509=x509, path=path, version=version, serial=x509.get_serial_number(), start=get_datetime_from_x509(x509.get_not_before()), end=get_datetime_from_x509(x509.get_not_after()), alt_name=self._read_alt_name(x509), subject=self._read_subject(x509), issuer=self._read_issuer(x509), ) return cert def _create_v1_prod_cert(self, version, extensions, x509, path): products = self._parse_v1_products(extensions) cert = ProductCertificate( x509=x509, path=path, version=version, serial=x509.get_serial_number(), start=get_datetime_from_x509(x509.get_not_before()), end=get_datetime_from_x509(x509.get_not_after()), products=products, subject=self._read_subject(x509), issuer=self._read_issuer(x509), ) return cert def _create_v1_ent_cert(self, version, extensions, x509, path): order = self._parse_v1_order(extensions) content = self._parse_v1_content(extensions) products = self._parse_v1_products(extensions) cert = EntitlementCertificate( x509=x509, path=path, version=version, serial=x509.get_serial_number(), start=get_datetime_from_x509(x509.get_not_before()), end=get_datetime_from_x509(x509.get_not_after()), subject=self._read_subject(x509), order=order, content=content, products=products, extensions=extensions, issuer=self._read_issuer(x509), ) return cert def _parse_v1_products(self, extensions): """ Returns an ordered list of all the product data in the certificate. """ products = [] for prod_namespace in extensions.find('1.*.1'): oid = prod_namespace[0] root = oid.rtrim(1) product_id = oid[1] ext = extensions.branch(root) products.append(Product( id=product_id, name=ext.get('1'), version=ext.get('2'), architectures=ext.get('3'), provided_tags=parse_tags(ext.get('4')), # not populated, only added for parity with # v3 product objects brand_type=ext.get('5'), brand_name=ext.get('6') )) return products def _parse_v1_order(self, extensions): order_extensions = extensions.branch(ORDER_NAMESPACE) order = Order( name=order_extensions.get('1'), number=order_extensions.get('2'), sku=order_extensions.get('3'), subscription=order_extensions.get('4'), quantity=order_extensions.get('5'), virt_limit=order_extensions.get('8'), socket_limit=order_extensions.get('9'), contract=order_extensions.get('10'), quantity_used=order_extensions.get('11'), warning_period=order_extensions.get('12'), account=order_extensions.get('13'), provides_management=order_extensions.get('14'), service_level=order_extensions.get('15'), service_type=order_extensions.get('16'), stacking_id=order_extensions.get('17'), virt_only=order_extensions.get('18') ) return order def _parse_v1_content(self, extensions): content = [] ents = extensions.find("2.*.*.1") for ent in ents: oid = ent[0].rtrim(1) content_ext = extensions.branch(oid) content.append(Content( content_type=extensions.get(oid), name=content_ext.get('1'), label=content_ext.get('2'), vendor=content_ext.get('5'), url=content_ext.get('6'), gpg=content_ext.get('7'), enabled=content_ext.get('8'), metadata_expire=content_ext.get('9'), required_tags=parse_tags(content_ext.get('10')), )) return content def _get_v1_cert_type(self, extensions): # Assume if there is an order name, it must be an entitlement cert: if EXT_ORDER_NAME in extensions: return ENTITLEMENT_CERT # If there is no order, but there are products, must be a product cert: elif len(extensions.find('1.*.1', 1, True)) > 0: return PRODUCT_CERT # Otherwise we assume it's a plain identity certificate: else: return IDENTITY_CERT def _create_v3_cert(self, version, extensions, x509, path, pem): # At this time, we only support v3 entitlement certificates try: # this is only expected to be available on the client side entitlement_data = pem.split("-----BEGIN ENTITLEMENT DATA-----")[1] entitlement_data = entitlement_data.split("-----END ENTITLEMENT DATA-----")[0].strip() except IndexError: entitlement_data = None if entitlement_data: payload = self._decompress_payload(base64.b64decode(entitlement_data)) order = self._parse_v3_order(payload) content = self._parse_v3_content(payload) products = self._parse_v3_products(payload) pool = self._parse_v3_pool(payload) else: order = None content = None products = None pool = None cert = EntitlementCertificate( x509=x509, path=path, version=version, extensions=extensions, serial=x509.get_serial_number(), start=get_datetime_from_x509(x509.get_not_before()), end=get_datetime_from_x509(x509.get_not_after()), subject=self._read_subject(x509), order=order, content=content, products=products, pool=pool, pem=pem, issuer=self._read_issuer(x509), ) return cert def _parse_v3_order(self, payload): sub = payload['subscription'] order = payload['order'] service_level = None service_type = None if 'service' in sub: service_level = sub['service'].get('level', None) service_type = sub['service'].get('type', None) return Order( name=sub['name'], number=order.get('number', None), sku=sub.get('sku', None), quantity=order.get('quantity', None), socket_limit=sub.get('sockets', None), contract=order.get('contract', None), quantity_used=payload.get('quantity', 1), warning_period=sub.get('warning', 0), account=order.get('account', None), provides_management=sub.get('management', False), service_level=service_level, service_type=service_type, stacking_id=sub.get('stacking_id', None), virt_only=sub.get('virt_only', False), ram_limit=sub.get('ram', None), core_limit=sub.get('cores', None) ) def _parse_v3_products(self, payload): """ Returns an ordered list of all the product data in the certificate. """ product_payload = payload['products'] products = [] for product in product_payload: products.append(Product( id=product['id'], name=product['name'], version=product.get('version', None), architectures=product.get('architectures', []), brand_type=product.get('brand_type', None), brand_name=product.get('brand_name', None) )) # TODO: skipping provided tags here, we don't yet generate # v3 product certs, we may never, which is the only place provided # tags can exist. return products def _parse_v3_content(self, payload): content = [] for product in payload['products']: for c in product['content']: content.append(Content( content_type=c['type'], name=c['name'], label=c['label'], vendor=c.get('vendor', None), url=c.get('path', None), gpg=c.get('gpg_url', None), enabled=c.get('enabled', True), metadata_expire=c.get('metadata_expire', None), required_tags=c.get('required_tags', []), arches=c.get('arches', []), )) return content def _parse_v3_pool(self, payload): pool = payload.get('pool', None) if pool: return Pool(id=pool['id']) return None def _decompress_payload(self, payload): """ Certificate payloads arrive in zlib compressed strings of JSON. This method de-compresses and parses the JSON and returns the resulting dict. """ try: decompressed = zlib.decompress(payload) return json.loads(decompressed) except Exception, e: log.exception(e) raise CertificateException("Error decompressing/parsing " "certificate payload.") class Version(object): """ Small wrapper for version string comparisons. """ def __init__(self, version_str): self.version_str = version_str self.segments = version_str.split(".") for i in range(len(self.segments)): self.segments[i] = int(self.segments[i]) self.major = self.segments[0] self.minor = 0 if len(self.segments) > 1: self.minor = self.segments[1] # TODO: comparator might be useful someday def __str__(self): return self.version_str class _Extensions2(Extensions): def _parse(self, x509): """ Override parent method for an X509 object from the new C wrapper. """ extensions = x509.get_all_extensions() for (key, value) in extensions.items(): oid = OID(key) self[oid] = value class Certificate(object): """ Parent class of all x509 certificate types. """ def __init__(self, x509=None, path=None, version=None, serial=None, start=None, end=None, subject=None, pem=None, issuer=None): # The X509 M2crypto object for this certificate. # WARNING: May be None in tests self.x509 = x509 # Full file path to the certificate on disk. May be None if the cert # hasn't yet been written to disk. self.path = path # Version of the certificate sent by Candlepin: self.version = version if serial is None: raise CertificateException("Certificate has no serial") self.serial = serial # Certificate start/end datetimes: self.start = start self.end = end self.valid_range = DateRange(self.start, self.end) self.pem = pem self.subject = subject self.issuer = issuer def is_valid(self, on_date=None): gmt = datetime.utcnow() if on_date: gmt = on_date gmt = gmt.replace(tzinfo=GMT()) return self.valid_range.has_date(gmt) def is_expired(self, on_date=None): gmt = datetime.utcnow() if on_date: gmt = on_date gmt = gmt.replace(tzinfo=GMT()) return self.valid_range.end() < gmt def __cmp__(self, other): if self.end < other.end: return -1 if self.end > other.end: return 1 return 0 def write(self, path): """ Write the certificate to disk. """ f = open(path, 'w') # if we were given the original pem, preserve it # ie for certv3 detached format. if self.pem is not None: f.write(self.pem) else: f.write(self.x509.as_pem()) f.close() self.path = path def delete(self): """ Delete the file associated with this certificate. """ if self.path: os.unlink(self.path) else: raise CertificateException('Certificate has no path, cannot delete.') class IdentityCertificate(Certificate): def __init__(self, alt_name=None, **kwargs): Certificate.__init__(self, **kwargs) self.alt_name = alt_name class ProductCertificate(Certificate): def __init__(self, products=None, **kwargs): Certificate.__init__(self, **kwargs) # The products in this certificate. The first is treated as the # primary or "marketing" product. if products is None: products = [] self.products = products class EntitlementCertificate(ProductCertificate): def __init__(self, order=None, content=None, pool=None, extensions=None, **kwargs): ProductCertificate.__init__(self, **kwargs) self.order = order self.content = content self.pool = pool self.extensions = extensions self._path_tree_object = None @property def _path_tree(self): """ :return: PathTree object built from this cert's extensions :rtype: rhsm.pathtree.PathTree :raise: AttributeError if self.version.major < 3 """ # This data was not present in certificates prior to v3. if self.version.major < 3: raise AttributeError( 'path tree not used for v%d certs' % self.version.major) if not self._path_tree_object: # generate and cache the tree data = self.extensions[EXT_ENT_PAYLOAD] self._path_tree_object = PathTree(data) return self._path_tree_object def is_expiring(self, on_date=None): gmt = datetime.utcnow() if on_date: gmt = on_date gmt = gmt.replace(tzinfo=GMT()) warning_time = timedelta(days=int(self.order.warning_period)) return self.valid_range.end() - warning_time < gmt def check_path(self, path): """ Checks the given path against the list of entitled paths as encoded in extensions. See PathTree for more detailed docs. :param path: path to which access is being requested :type path: basestring :return: True iff the path matches, else False :rtype: bool :raise: ValueError when self.version.major < 3 """ # squash double '//' if we get it in a content path # NOTE: according to http://tools.ietf.org/html/rfc3986#section-3.3 # I think this technically changes the semantics of the url path = posixpath.normpath(path) if self.version.major < 3: return self._check_v1_path(path) else: return self._path_tree.match_path(path) def _check_v1_path(self, path): """ Check the requested path against a v1 certificate :param path: requested path :type path: basestring :return: True iff the path matches, else False :rtype: bool """ path = path.strip('/') valid = False for ext_oid, oid_url in self.extensions.iteritems(): # if this is a download URL if ext_oid.match(OID('2.')) and ext_oid.match(OID('.1.6')): if self._validate_v1_url(oid_url, path): valid = True break return valid @staticmethod def _validate_v1_url(oid_url, dest): """ Determines if the destination URL matches the OID's URL. Swaps out all $ variables (e.g. $basearch, $version) for a reg ex wildcard in that location. For example, the following entitlement: content/dist/rhel/server/$version/$basearch/os Should allow any value for the variables: content/dist/rhel/server/.+?/.+?/os :param oid_url: path associated with an entitlement OID, as pulled from the cert's extensions. :type oid_url: basestring :param dest: path requested by a client :type dest: basestring :return: True iff the OID permits the destination else False :rtype: bool """ # Remove initial and trailing '/', and substitute the $variables for # equivalent regular expressions in oid_url. oid_re = re.sub(r'\$[^/]+(/|$)', '[^/]+/', oid_url.strip('/')) return re.match(oid_re, dest) is not None def delete(self): """ Override parent to also delete certificate key. """ Certificate.delete(self) # Can assume we have a path here, super method would have thrown # Exception if we didn't: key_path = self.key_path() os.unlink(key_path) def key_path(self): """ Returns the full path to the cert key's pem. """ dir_path, cert_filename = os.path.split(self.path) try: key_filename = "%s-key.%s" % tuple(cert_filename.rsplit(".", 1)) except TypeError, e: log.exception(e) raise CertificateException("Entitlement certificate path \"%s\" is not in " "in the expected format so the key file path " "could not be based on it." % self.path) key_path = os.path.join(dir_path, key_filename) return key_path class Product(object): """ Represents the product information from a certificate. """ def __init__(self, id=None, name=None, version=None, architectures=None, provided_tags=None, brand_type=None, brand_name=None): if name is None: raise CertificateException("Product missing name") if id is None: raise CertificateException("Product missing ID") self.id = id self.name = name self.version = version self.architectures = architectures # If this is sent in as a string split it, as the field # can technically be multi-valued: if isinstance(self.architectures, str): self.architectures = parse_tags(self.architectures) if self.architectures is None: self.architectures = [] self.provided_tags = provided_tags if self.provided_tags is None: self.provided_tags = [] self.brand_type = brand_type self.brand_name = brand_name def __eq__(self, other): return (self.id == other.id) class Order(object): """ Represents the order information for the subscription an entitlement originated from. """ def __init__(self, name=None, number=None, sku=None, subscription=None, quantity=None, virt_limit=None, socket_limit=None, contract=None, quantity_used=None, warning_period=None, account=None, provides_management=None, service_level=None, service_type=None, stacking_id=None, virt_only=None, ram_limit=None, core_limit=None): self.name = name self.number = number # order number self.sku = sku # aka the marketing product self.subscription = subscription # seems to be unused # total quantity on the order: self.quantity = safe_int(quantity, None) # rarely used # actual quantity used by this entitlement: self.quantity_used = safe_int(quantity_used, 1) self.virt_limit = virt_limit # unused self.stacking_id = stacking_id self.socket_limit = safe_int(socket_limit, None) self.warning_period = safe_int(warning_period, 0) self.contract = contract self.account = account self.provides_management = provides_management or False self.service_level = service_level self.service_type = service_type self.virt_only = virt_only or False self.ram_limit = safe_int(ram_limit, None) self.core_limit = safe_int(core_limit, None) def __str__(self): return "<Order: name=%s number=%s sku=%s>" % \ (self.name, self.number, self.sku) class Content(object): def __init__(self, content_type=None, name=None, label=None, vendor=None, url=None, gpg=None, enabled=None, metadata_expire=None, required_tags=None, arches=None): if (name is None) or (label is None): raise CertificateException("Content missing name/label") self.content_type = content_type self.name = name self.label = label self.vendor = vendor self.url = url self.gpg = gpg if not content_type: raise CertificateException("Content does not have a type set.") if (enabled not in (None, 0, 1, "0", "1")): raise CertificateException("Invalid content enabled setting: %s" % enabled) # Convert possible incoming None or string (0/1) to a boolean: # If enabled isn't specified in cert we assume True. self.enabled = False if enabled is None or enabled == "1" or enabled is True: self.enabled = True self.metadata_expire = metadata_expire self.required_tags = required_tags or [] self.arches = arches or [] def __eq__(self, other): return isinstance(other, self.__class__) and (self.label == other.label) def __str__(self): return "<Content: content_type=%s name=%s label=%s enabled=%s>" % \ (self.content_type, self.name, self.label, self.enabled) def __hash__(self): return hash(self.label) class Pool(object): """ Represents the pool an entitlement originates from. """ def __init__(self, id=None): if id is None: raise CertificateException("Pool is missing ID") self.id = id def __eq__(self, other): return (self.id == other.id)