src/modules/client/publisher.py
changeset 2215 b4355e8c5097
parent 2100 6a366b063036
child 2219 60ad60f7592c
equal deleted inserted replaced
2214:4908a492a0fb 2215:b4355e8c5097
    19 #
    19 #
    20 # CDDL HEADER END
    20 # CDDL HEADER END
    21 #
    21 #
    22 
    22 
    23 #
    23 #
    24 # Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
    24 # Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
    25 #
    25 #
    26 
    26 
    27 #
    27 #
    28 # NOTE: Any changes to this file are considered a change in client api
    28 # NOTE: Any changes to this file are considered a change in client api
    29 # interfaces and must be fully documented in doc/client_api_versions.txt
    29 # interfaces and must be fully documented in doc/client_api_versions.txt
    90 
    90 
    91 # Sort policy mapping.
    91 # Sort policy mapping.
    92 URI_SORT_POLICIES = {
    92 URI_SORT_POLICIES = {
    93     URI_SORT_PRIORITY: lambda obj: (obj.priority, obj.uri),
    93     URI_SORT_PRIORITY: lambda obj: (obj.priority, obj.uri),
    94 }
    94 }
       
    95 
       
    96 # This dictionary records the recognized values of extensions.
       
    97 SUPPORTED_EXTENSION_VALUES = {
       
    98     "basicConstraints": ("CA:TRUE", "CA:FALSE"),
       
    99     "keyUsage": ("DIGITAL SIGNATURE", "CERTIFICATE SIGN", "CRL SIGN")
       
   100 }
       
   101 
       
   102 # These dictionaries map uses into their extensions.
       
   103 CODE_SIGNING_USE = {
       
   104     "keyUsage": ["DIGITAL SIGNATURE"]
       
   105 }
       
   106 
       
   107 CERT_SIGNING_USE = {
       
   108     "basicConstraints": ["CA:TRUE"],
       
   109     "keyUsage": ["CERTIFICATE SIGN"]
       
   110 }
       
   111 
       
   112 CRL_SIGNING_USE = {
       
   113     "keyUsage": ["CRL SIGN"]
       
   114 }
       
   115 
       
   116 POSSIBLE_USES = [CODE_SIGNING_USE, CERT_SIGNING_USE, CRL_SIGNING_USE]
    95 
   117 
    96 class RepositoryURI(object):
   118 class RepositoryURI(object):
    97         """Class representing a repository URI and any transport-related
   119         """Class representing a repository URI and any transport-related
    98         information."""
   120         information."""
    99 
   121 
  1683                 # Verifying this one certificate shouldn't change whether
  1705                 # Verifying this one certificate shouldn't change whether
  1684                 # the publisher's CA certs have been verified.
  1706                 # the publisher's CA certs have been verified.
  1685                 old_verification_value = self.__verified_cas
  1707                 old_verification_value = self.__verified_cas
  1686                 # Mark that not all CA certs have been verified.
  1708                 # Mark that not all CA certs have been verified.
  1687                 self.__verified_cas = False
  1709                 self.__verified_cas = False
  1688                 hsh = self.add_cert(cert)
  1710                 hsh = self.__add_cert(cert)
  1689                 self.signing_ca_certs.append(hsh)
  1711                 self.signing_ca_certs.append(hsh)
  1690                 # If the user had previously removed this certificate, remove
  1712                 # If the user had previously removed this certificate, remove
  1691                 # the certificate from that list.
  1713                 # the certificate from that list.
  1692                 if manual and hsh in self.revoked_ca_certs:
  1714                 if manual and hsh in self.revoked_ca_certs:
  1693                         t = set(self.revoked_ca_certs)
  1715                         t = set(self.revoked_ca_certs)
  1729                 if s in self.revoked_ca_certs:
  1751                 if s in self.revoked_ca_certs:
  1730                         t = set(self.revoked_ca_certs)
  1752                         t = set(self.revoked_ca_certs)
  1731                         t.remove(s)
  1753                         t.remove(s)
  1732                         self.revoked_ca_certs = list(t)
  1754                         self.revoked_ca_certs = list(t)
  1733 
  1755 
  1734         def add_cert(self, s):
  1756         def __add_cert(self, s):
  1735                 """Add the certificate stored as a string in 's' to the
  1757                 """Add the certificate stored as a string in 's' to the
  1736                 certificates this publisher knows about."""
  1758                 certificates this publisher knows about."""
  1737 
  1759 
  1738                 self.create_meta_root()
  1760                 self.create_meta_root()
  1739                 pkg_hash = hashlib.sha1()
  1761                 pkg_hash = hashlib.sha1()
  1793                 is only stored on disk. """
  1815                 is only stored on disk. """
  1794 
  1816 
  1795                 assert not (verify_hash and only_retrieve)
  1817                 assert not (verify_hash and only_retrieve)
  1796                 pth = os.path.join(self.cert_root, pkg_hash)
  1818                 pth = os.path.join(self.cert_root, pkg_hash)
  1797                 if not os.path.exists(pth):
  1819                 if not os.path.exists(pth):
  1798                         self.add_cert(self.transport.get_content(self,
  1820                         self.__add_cert(self.transport.get_content(self,
  1799                             pkg_hash))
  1821                             pkg_hash))
  1800                 if only_retrieve:
  1822                 if only_retrieve:
  1801                         return None
  1823                         return None
  1802                 with open(pth, "rb") as fh:
  1824                 with open(pth, "rb") as fh:
  1803                         s = fh.read()
  1825                         s = fh.read()
  1809                         if h != pkg_hash:
  1831                         if h != pkg_hash:
  1810                                 raise api_errors.ModifiedCertificateException(c,
  1832                                 raise api_errors.ModifiedCertificateException(c,
  1811                                     pth)
  1833                                     pth)
  1812                 return c
  1834                 return c
  1813 
  1835 
  1814         def get_certs_by_name(self, name):
  1836         def __get_certs_by_name(self, name):
  1815                 """Given 'name', a M2Crypto X509_Name, return the certs with
  1837                 """Given 'name', a M2Crypto X509_Name, return the certs with
  1816                 that name as a subject."""
  1838                 that name as a subject."""
  1817 
  1839 
  1818                 res = []
  1840                 res = []
  1819                 c = 0
  1841                 c = 0
  1918                 # declared minus the ones the user has explictly removed.
  1940                 # declared minus the ones the user has explictly removed.
  1919                 for c in set(self.signing_ca_certs) - \
  1941                 for c in set(self.signing_ca_certs) - \
  1920                     set(self.revoked_ca_certs):
  1942                     set(self.revoked_ca_certs):
  1921                         cert = self.get_cert_by_hash(c, verify_hash=True)
  1943                         cert = self.get_cert_by_hash(c, verify_hash=True)
  1922                         try:
  1944                         try:
  1923                                 self.verify_chain(cert, trust_anchors)
  1945                                 self.verify_chain(cert, trust_anchors,
       
  1946                                     usages=CERT_SIGNING_USE)
  1924                         except api_errors.CertificateException:
  1947                         except api_errors.CertificateException:
  1925                                 # If the cert couldn't be verified, add it to
  1948                                 # If the cert couldn't be verified, add it to
  1926                                 # the certs to ignore for this operation but
  1949                                 # the certs to ignore for this operation but
  1927                                 # don't treat it as if the user had declared
  1950                                 # don't treat it as if the user had declared
  1928                                 # the cert untrustworthy.
  1951                                 # the cert untrustworthy.
  1954                         except m2.X509.X509Error:
  1977                         except m2.X509.X509Error:
  1955                                 raise api_errors.BadFileFormat(_("The CRL file "
  1978                                 raise api_errors.BadFileFormat(_("The CRL file "
  1956                                     "%s is not in a recognized format.") %
  1979                                     "%s is not in a recognized format.") %
  1957                                     pth)
  1980                                     pth)
  1958 
  1981 
  1959         def get_crl(self, uri):
  1982         def __get_crl(self, uri):
  1960                 """Given a URI (for now only http URIs are supported), return
  1983                 """Given a URI (for now only http URIs are supported), return
  1961                 the CRL object created from the file stored at that uri."""
  1984                 the CRL object created from the file stored at that uri."""
  1962 
  1985 
  1963                 if uri.startswith("URI:"):
  1986                 if uri.startswith("URI:"):
  1964                         uri = uri[4:]
  1987                         uri = uri[4:]
  2023                 try:
  2046                 try:
  2024                         ext = cert.get_ext("crlDistributionPoints")
  2047                         ext = cert.get_ext("crlDistributionPoints")
  2025                 except LookupError, e:
  2048                 except LookupError, e:
  2026                         return True
  2049                         return True
  2027                 uri = ext.get_value()
  2050                 uri = ext.get_value()
  2028                 crl = self.get_crl(uri)
  2051                 crl = self.__get_crl(uri)
  2029                 # If we couldn't retrieve a CRL from the distribution point
  2052                 # If we couldn't retrieve a CRL from the distribution point
  2030                 # and no CRL is cached on disk, assume the cert has not been
  2053                 # and no CRL is cached on disk, assume the cert has not been
  2031                 # revoked.  It's possible that this should be an image or
  2054                 # revoked.  It's possible that this should be an image or
  2032                 # publisher setting in the future.
  2055                 # publisher setting in the future.
  2033                 if not crl:
  2056                 if not crl:
  2037                 # a certificate is.
  2060                 # a certificate is.
  2038                 verified_crl = False
  2061                 verified_crl = False
  2039                 crl_issuer = crl.get_issuer()
  2062                 crl_issuer = crl.get_issuer()
  2040                 tas = ca_dict.get(crl_issuer.as_hash(), [])
  2063                 tas = ca_dict.get(crl_issuer.as_hash(), [])
  2041                 for t in tas:
  2064                 for t in tas:
  2042                         if crl.verify(t.get_pubkey()):
  2065                         try:
  2043                                 verified_crl = True
  2066                                 if crl.verify(t.get_pubkey()):
       
  2067                                         # If t isn't approved for signing crls,
       
  2068                                         # the exception __check_extensions
       
  2069                                         # raises will take the code to the
       
  2070                                         # except below.
       
  2071                                         self.__check_extensions(t,
       
  2072                                             CRL_SIGNING_USE)
       
  2073                                         verified_crl = True
       
  2074                         except api_errors.SigningException:
       
  2075                                 pass
  2044                 if not verified_crl:
  2076                 if not verified_crl:
  2045                         crl_cas = self.get_certs_by_name(crl_issuer)
  2077                         crl_cas = self.__get_certs_by_name(crl_issuer)
  2046                         for c in crl_cas:
  2078                         for c in crl_cas:
  2047                                 if crl.verify(c.get_pubkey()):
  2079                                 if crl.verify(c.get_pubkey()):
  2048                                         try:
  2080                                         try:
  2049                                                 self.verify_chain(c, ca_dict)
  2081                                                 self.verify_chain(c, ca_dict,
       
  2082                                                     usages=CRL_SIGNING_USE)
  2050                                         except api_errors.SigningException:
  2083                                         except api_errors.SigningException:
  2051                                                 pass
  2084                                                 pass
  2052                                         else:
  2085                                         else:
  2053                                                 verified_crl = True
  2086                                                 verified_crl = True
  2054                                                 break
  2087                                                 break
  2058                 # and revoked the certificate.
  2091                 # and revoked the certificate.
  2059                 rev = crl.is_revoked(cert)
  2092                 rev = crl.is_revoked(cert)
  2060                 if rev:
  2093                 if rev:
  2061                         raise api_errors.RevokedCertificate(cert, rev[1])
  2094                         raise api_errors.RevokedCertificate(cert, rev[1])
  2062 
  2095 
  2063         def check_critical(self, ext):
  2096         def __check_extensions(self, cert, usages):
  2064                 """Check whether this criticial extension is supported."""
       
  2065 
       
  2066                 if ext.get_name() != "basicConstraints":
       
  2067                         return False
       
  2068                 v = ext.get_value()
       
  2069                 if v.upper() not in ("CA:TRUE", "CA:FALSE"):
       
  2070                         return False
       
  2071                 return True
       
  2072 
       
  2073         def check_extensions(self, cert):
       
  2074                 """Check whether the critical extensions in this certificate
  2097                 """Check whether the critical extensions in this certificate
  2075                 are supported."""
  2098                 are supported and allow the provided use(s)."""
  2076 
  2099 
  2077                 for i in range(0, cert.get_ext_count()):
  2100                 for i in range(0, cert.get_ext_count()):
  2078                         ext = cert.get_ext_at(i)
  2101                         ext = cert.get_ext_at(i)
  2079                         if not ext.get_critical() or self.check_critical(ext):
  2102                         name = ext.get_name()
  2080                                 continue
  2103                         v = ext.get_value().upper()
  2081                         raise api_errors.UnsupportedCriticalExtension(cert, ext)
  2104                         # Check whether the extension name is recognized.
       
  2105                         if name in SUPPORTED_EXTENSION_VALUES:
       
  2106                                 supported_vs = \
       
  2107                                     SUPPORTED_EXTENSION_VALUES[name]
       
  2108                                 vs = [s.strip() for s in v.split(",")]
       
  2109                                 # Check whether the values for the extension are
       
  2110                                 # recognized.
       
  2111                                 for v in vs:
       
  2112                                         if v not in supported_vs:
       
  2113                                                 if len(vs) < 2:
       
  2114                                                         raise api_errors.UnsupportedExtensionValue(cert, ext)
       
  2115                                                 else:
       
  2116                                                         raise api_errors.UnsupportedExtensionValue(cert, ext, v)
       
  2117                                 uses = usages.get(name, [])
       
  2118                                 if isinstance(uses, basestring):
       
  2119                                         uses = [uses]
       
  2120                                 # For each use, check to see whether it's
       
  2121                                 # permitted by the certificate's extension
       
  2122                                 # values.
       
  2123                                 for u in uses:
       
  2124                                         if u not in vs:
       
  2125                                                 raise api_errors.InappropriateCertificateUse(cert, ext, u)
       
  2126                         # If the extension name is unrecognized and critical,
       
  2127                         # then the chain cannot be verified.
       
  2128                         elif ext.get_critical():
       
  2129                                 raise api_errors.UnsupportedCriticalExtension(
       
  2130                                     cert, ext)
  2082         
  2131         
  2083         def verify_chain(self, cert, ca_dict, required_names=None):
  2132         def verify_chain(self, cert, ca_dict, required_names=None, usages=None):
  2084                 """Validates the certificate against the given trust anchors.
  2133                 """Validates the certificate against the given trust anchors.
  2085 
  2134 
  2086                 The 'cert' parameter is the certificate to validate.
  2135                 The 'cert' parameter is the certificate to validate.
  2087 
  2136 
  2088                 The 'ca_dict' is a dictionary which maps subject hashes to
  2137                 The 'ca_dict' is a dictionary which maps subject hashes to
  2095                         required_names = set()
  2144                         required_names = set()
  2096                 verified = False
  2145                 verified = False
  2097                 continue_loop = True
  2146                 continue_loop = True
  2098                 certs_with_problems = []
  2147                 certs_with_problems = []
  2099 
  2148 
       
  2149                 def merge_dicts(d1, d2):
       
  2150                         """Function for merging usage dictionaries."""
       
  2151                         res = copy.deepcopy(d1)
       
  2152                         for k in d2:
       
  2153                                 if k in res:
       
  2154                                         res[k].extend(d2[k])
       
  2155                                 else:
       
  2156                                         res[k] = d2[k]
       
  2157                         return res
       
  2158 
       
  2159                 if not usages:
       
  2160                         usages = {}
       
  2161                         for u in POSSIBLE_USES:
       
  2162                                 usages = merge_dicts(usages, u)
       
  2163 
  2100                 # Check whether we can validate this certificate.
  2164                 # Check whether we can validate this certificate.
  2101                 self.check_extensions(cert)
  2165                 self.__check_extensions(cert, usages)
  2102 
  2166 
  2103                 # Check whether this certificate has been revoked.
  2167                 # Check whether this certificate has been revoked.
  2104                 self.__check_crls(cert, ca_dict)
  2168                 self.__check_crls(cert, ca_dict)
  2105 
  2169 
  2106                 while continue_loop:
  2170                 while continue_loop:
  2150                                 up_chain = False
  2214                                 up_chain = False
  2151                                 # Keep track of certs that would have verified
  2215                                 # Keep track of certs that would have verified
  2152                                 # this certificate but had critical extensions
  2216                                 # this certificate but had critical extensions
  2153                                 # we can't handle yet for error reporting.
  2217                                 # we can't handle yet for error reporting.
  2154                                 certs_with_problems = []
  2218                                 certs_with_problems = []
  2155                                 for c in self.get_certs_by_name(issuer):
  2219                                 for c in self.__get_certs_by_name(issuer):
  2156                                         # If the certificate is approved to
  2220                                         # If the certificate is approved to
  2157                                         # sign another certificate, verifies
  2221                                         # sign another certificate, verifies
  2158                                         # the current certificate, and hasn't
  2222                                         # the current certificate, and hasn't
  2159                                         # been revoked, consider it as the
  2223                                         # been revoked, consider it as the
  2160                                         # next link in the chain.
  2224                                         # next link in the chain.  check_ca
       
  2225                                         # checks both the basicConstraints
       
  2226                                         # extension and the keyUsage extension.
  2161                                         if c.check_ca() and \
  2227                                         if c.check_ca() and \
  2162                                             cert.verify(c.get_pubkey()):
  2228                                             cert.verify(c.get_pubkey()):
  2163                                                 problem = False
  2229                                                 problem = False
  2164                                                 # Check whether this certificate
  2230                                                 # Check whether this certificate
  2165                                                 # has a critical extension we
  2231                                                 # has a critical extension we
  2166                                                 # don't understand.
  2232                                                 # don't understand.
  2167                                                 try:
  2233                                                 try:
  2168                                                         self.check_extensions(c)
  2234                                                         self.__check_extensions(
       
  2235                                                             c, CERT_SIGNING_USE)
  2169                                                         self.__check_crls(c,
  2236                                                         self.__check_crls(c,
  2170                                                             ca_dict)
  2237                                                             ca_dict)
  2171                                                 except (api_errors.UnsupportedCriticalExtension, api_errors.RevokedCertificate), e:
  2238                                                 except (api_errors.UnsupportedCriticalExtension, api_errors.RevokedCertificate), e:
  2172                                                         certs_with_problems.append(e)
  2239                                                         certs_with_problems.append(e)
  2173                                                         problem = True
  2240                                                         problem = True