|
1 | 1 | from dataclasses import dataclass |
2 | | -from typing import List, Optional, Any, Set |
| 2 | +from typing import List, Optional, Any, Set, cast |
3 | 3 |
|
| 4 | +from cryptography.hazmat._oid import AuthorityInformationAccessOID, ExtensionOID |
| 5 | +from cryptography.x509 import AuthorityInformationAccess, ExtensionNotFound |
4 | 6 | from nassl.ephemeral_key_info import EcDhEphemeralKeyInfo, DhEphemeralKeyInfo, OpenSslEvpPkeyEnum |
5 | | -from sslyze import TlsVersionEnum, CipherSuiteAcceptedByServer, CipherSuite |
| 7 | +from sslyze import TlsVersionEnum, CipherSuiteAcceptedByServer, CipherSuite, CertificateDeploymentAnalysisResult |
6 | 8 | from sslyze.plugins.openssl_cipher_suites.cipher_suites import _TLS_1_3_CIPHER_SUITES |
7 | 9 |
|
8 | 10 | from checks import scoring |
9 | | -from checks.models import KexHashFuncStatus, CipherOrderStatus |
| 11 | +from checks.models import KexHashFuncStatus, CipherOrderStatus, OcspStatus |
10 | 12 | from checks.tasks.tls.tls_constants import ( |
11 | 13 | PROTOCOLS_GOOD, |
12 | 14 | PROTOCOLS_SUFFICIENT, |
@@ -197,6 +199,58 @@ def score(self) -> scoring.Score: |
197 | 199 | return scoring.WEB_TLS_SUITES_BAD if self.ciphers_bad else scoring.WEB_TLS_SUITES_GOOD |
198 | 200 |
|
199 | 201 |
|
| 202 | +@dataclass(frozen=True) |
| 203 | +class TLSOCSPEvaluation: |
| 204 | + """ |
| 205 | + Evaluate the OCSP setup, based on certificate info. |
| 206 | + """ |
| 207 | + |
| 208 | + ocsp_in_cert: bool |
| 209 | + has_ocsp_response: bool |
| 210 | + ocsp_response_trusted: bool |
| 211 | + |
| 212 | + GOOD_STATUSES = {OcspStatus.good, OcspStatus.not_in_cert} |
| 213 | + |
| 214 | + @classmethod |
| 215 | + def from_certificate_deployments(cls, certificate_deployment: CertificateDeploymentAnalysisResult): |
| 216 | + leaf_cert = certificate_deployment.received_certificate_chain[0] |
| 217 | + try: |
| 218 | + aia_extension = leaf_cert.extensions.get_extension_for_oid(ExtensionOID.AUTHORITY_INFORMATION_ACCESS) |
| 219 | + aia_value = cast(AuthorityInformationAccess, aia_extension.value) |
| 220 | + ocsp_access = [ad for ad in aia_value if ad.access_method == AuthorityInformationAccessOID.OCSP] |
| 221 | + ocsp_in_cert = len(ocsp_access) > 0 |
| 222 | + except ExtensionNotFound: |
| 223 | + ocsp_in_cert = False |
| 224 | + |
| 225 | + has_ocsp_response = certificate_deployment.ocsp_response is not None |
| 226 | + ocsp_response_trusted = certificate_deployment.ocsp_response is True |
| 227 | + |
| 228 | + return cls( |
| 229 | + ocsp_in_cert=ocsp_in_cert, |
| 230 | + has_ocsp_response=has_ocsp_response, |
| 231 | + ocsp_response_trusted=ocsp_response_trusted, |
| 232 | + ) |
| 233 | + |
| 234 | + @property |
| 235 | + def status(self) -> OcspStatus: |
| 236 | + if not self.ocsp_in_cert: |
| 237 | + return OcspStatus.not_in_cert |
| 238 | + if self.has_ocsp_response: |
| 239 | + if self.ocsp_response_trusted: |
| 240 | + return OcspStatus.good |
| 241 | + else: |
| 242 | + return OcspStatus.not_trusted |
| 243 | + return OcspStatus.ok |
| 244 | + |
| 245 | + @property |
| 246 | + def score(self) -> scoring.Score: |
| 247 | + return ( |
| 248 | + scoring.WEB_TLS_OCSP_STAPLING_GOOD |
| 249 | + if self.status in self.GOOD_STATUSES |
| 250 | + else scoring.WEB_TLS_OCSP_STAPLING_BAD |
| 251 | + ) |
| 252 | + |
| 253 | + |
200 | 254 | @dataclass(frozen=True) |
201 | 255 | class KeyExchangeHashFunctionEvaluation: |
202 | 256 | """ |
|
0 commit comments