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() |
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 |