def display(msg: str, issue: Issue) -> None: if issue.vulnerability.display_all or not is_registered( issue.vulnerability): if issue.severity == Severity.CRITICAL or issue.severity == Severity.HIGH: output.vuln(msg) elif issue.severity == Severity.MEDIUM: output.warn(msg) else: output.info(msg) # if there's no evidence, default to the msg - better than nothing if issue.evidence is None: issue.evidence = msg.strip() register(issue)
def _get_vulnerability_info(ep, url): output.norm("\tProtocol & Vulnerability Information:") if "sniRequired" in ep["details"]: if ep["details"]["sniRequired"]: output.info("\t\tSNI Required: Yes") else: output.norm("\t\tSNI Required: No") if "drownVulnerable" in ep["details"]: if ep["details"]["drownVulnerable"]: output.vuln("\t\tDROWN: Vulnerable") servers = "" for dh in ep["details"]["drownHosts"]: servers += f'({dh["ip"]}:{dh["port"]} - {dh["status"]}) ' output.norm(f'\t\t\t{dh["ip"]}:{dh["port"]} - {dh["status"]}') output.norm( f'\t\t\t https://test.drownattack.com/?site={dh["ip"]}') reporter.register( issue.Issue( Vulnerabilities.TLS_DROWN, url, { "servers": servers, "ip": ep["ipAddress"] }, )) else: output.norm("\t\tDROWN: No") else: output.error("\t\tDROWN: Information Not Received") if "zeroRTTEnabled" in ep["details"]: if ep["details"]["zeroRTTEnabled"] == -2: output.error("\t\tTLS 1.3 0-RTT Support: Test Failed") elif ep["details"]["zeroRTTEnabled"] == -1: output.norm("\t\tTLS 1.3 0-RTT Support: Test Not Performed") elif ep["details"]["zeroRTTEnabled"] == 0: output.norm("\t\tTLS 1.3 0-RTT Support: No") elif ep["details"]["zeroRTTEnabled"] == 1: reporter.display( "\t\tTLS 1.3 0-RTT Support: Yes", issue.Issue( Vulnerabilities.TLS_VERSION_1_3_EARLY_DATA_ENABLED, url, {"ip": ep["ipAddress"]}, ), ) else: output.error( f'\t\tTLS 1.3 0-RTT Support: Unknown ({ep["details"]["zeroRTTEnabled"]})' ) else: output.error("\t\tTLS 1.3 0-RTT Support: Information Not Received") if "renegSupport" in ep["details"]: if ep["details"]["renegSupport"] & 1 != 0: reporter.display( "\t\tSecure Renegotiation: insecure client-initiated renegotiation supported", issue.Issue(Vulnerabilities.TLS_INSECURE_RENEG, url, {"ip": ep["ipAddress"]}), ) if ep["details"]["renegSupport"] & (1 << 1) != 0: output.norm( "\t\tSecure Renegotiation: secure renegotiation supported") if ep["details"]["renegSupport"] & (1 << 2) != 0: output.norm( "\t\tSecure Renegotiation: secure client-initiated renegotiation supported" ) if ep["details"]["renegSupport"] & (1 << 3) != 0: output.norm( '\t\tSecure Renegotiation: server requires secure renegotiation support"' ) else: output.error("\t\tSecure Renegotiation: Information Not Received") if "poodle" in ep["details"]: if ep["details"]["poodle"]: reporter.display( "\t\tPOODLE (SSL): Vulnerable", issue.Issue(Vulnerabilities.TLS_LEGACY_SSL_POODLE, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tPOODLE (SSL): No") else: output.error("\t\tPOODLE (SSL): Information Not Received") if "zombiePoodle" in ep["details"]: if ep["details"]["zombiePoodle"] == -1: output.error("\t\tZombie POODLE: Test Failed") elif ep["details"]["zombiePoodle"] == 0: output.error("\t\tZombie POODLE: Test Failed (Unknown)") elif ep["details"]["zombiePoodle"] == 1: output.norm("\t\tZombie POODLE: No") elif ep["details"]["zombiePoodle"] == 2: reporter.display( "\t\tZombie POODLE: Vulnerable - Not Exploitable", issue.Issue(Vulnerabilities.TLS_ZOMBIE_POODLE_NE, url, {"ip": ep["ipAddress"]}), ) elif ep["details"]["zombiePoodle"] == 3: output.vuln("\t\tZombie POODLE: Vulnerable - Exploitable") reporter.display( "\t\tZombie POODLE: Vulnerable - Exploitable", issue.Issue(Vulnerabilities.TLS_ZOMBIE_POODLE, url, {"ip": ep["ipAddress"]}), ) else: output.error( f'\t\tZombie POODLE: Unknown Response ({ep["details"]["zombiePoodle"]})' ) else: output.error("\t\tZombie POODLE: Information Not Received") if "goldenDoodle" in ep["details"]: if ep["details"]["goldenDoodle"] == -1: output.error("\t\tGOLDENDOODLE: Test Failed") elif ep["details"]["goldenDoodle"] == 0: output.error("\t\tGOLDENDOODLE: Test Failed (Unknown)") elif ep["details"]["goldenDoodle"] == 1: output.norm("\t\tGOLDENDOODLE: No") elif ep["details"]["goldenDoodle"] == 4: reporter.display( "\t\tGOLDENDOODLE: Vulnerable - Not Exploitable", issue.Issue(Vulnerabilities.TLS_GOLDENDOODLE_NE, url, {"ip": ep["ipAddress"]}), ) elif ep["details"]["goldenDoodle"] == 5: reporter.display( "\t\tGOLDENDOODLE: Vulnerable - Exploitable", issue.Issue(Vulnerabilities.TLS_GOLDENDOODLE, url, {"ip": ep["ipAddress"]}), ) else: output.error( f't\tGOLDENDOODLE: Unknown Response ({ep["details"]["goldenDoodle"]})' ) else: output.error("\t\tGOLDENDOODLE: Information Not Received") if "zeroLengthPaddingOracle" in ep["details"]: if ep["details"]["zeroLengthPaddingOracle"] == -1: output.error( "\t\tOpenSSL 0-Length Padding Oracle (CVE-2019-1559): Test Failed" ) elif ep["details"]["zeroLengthPaddingOracle"] == 0: output.error( "\t\tOpenSSL 0-Length Padding Oracle (CVE-2019-1559): Test Failed (Unknown)" ) elif ep["details"]["zeroLengthPaddingOracle"] == 1: output.norm( "\t\tOpenSSL 0-Length Padding Oracle (CVE-2019-1559): No") elif ep["details"]["zeroLengthPaddingOracle"] == 6: reporter.display( "\t\tOpenSSL 0-Length Padding Oracle (CVE-2019-1559): Vulnerable - Not Exploitable", issue.Issue( Vulnerabilities.TLS_OPENSSL_CVE_2019_1559_NE, url, {"ip": ep["ipAddress"]}, ), ) elif ep["details"]["zeroLengthPaddingOracle"] == 7: reporter.display( "\t\tOpenSSL 0-Length Padding Oracle (CVE-2019-1559): Vulnerable - Exploitable", issue.Issue( Vulnerabilities.TLS_OPENSSL_CVE_2019_1559, url, {"ip": ep["ipAddress"]}, ), ) else: output.error( f"\t\tOpenSSL 0-Length Padding Oracle (CVE-2019-1559): Unknown Response" f' ({ep["details"]["zeroLengthPaddingOracle"]})') else: output.error( "OpenSSL 0-Length Padding Oracle (CVE-2019-1559): Information Not Received" ) if "sleepingPoodle" in ep["details"]: if ep["details"]["sleepingPoodle"] == -1: output.error("\t\tSleeping POODLE: Test Failed") elif ep["details"]["sleepingPoodle"] == 0: output.error("\t\tSleeping POODLE: Test Failed (Unknown)") elif ep["details"]["sleepingPoodle"] == 1: output.norm("\t\tSleeping POODLE: No") elif ep["details"]["sleepingPoodle"] == 10: reporter.display( "\t\tSleeping POODLE: Vulnerable - Not Exploitable", issue.Issue(Vulnerabilities.TLS_SLEEPING_POODLE_NE, url, {"ip": ep["ipAddress"]}), ) elif ep["details"]["sleepingPoodle"] == 11: output.vuln("\t\tSleeping POODLE: Vulnerable - Exploitable") reporter.display( "\t\tSleeping POODLE: Vulnerable - Exploitable", issue.Issue(Vulnerabilities.TLS_SLEEPING_POODLE, url, {"ip": ep["ipAddress"]}), ) else: output.error( f'\t\tSleeping POODLE: Unknown Response ({ep["details"]["sleepingPoodle"]})' ) else: output.error("\t\tSleeping POODLE: Information Not Received") if "poodleTls" in ep["details"]: if ep["details"]["poodleTls"] == -3: output.info("\t\tPOODLE (TLS): Inconclusive (Timeout)") elif ep["details"]["poodleTls"] == -2: output.info("\t\tPOODLE (TLS): TLS Not Supported") elif ep["details"]["poodleTls"] == -1: output.error("\t\tPOODLE (TLS): Test Failed") elif ep["details"]["poodleTls"] == 0: output.error("\t\tPOODLE (TLS): Test Failed (Unknown)") elif ep["details"]["poodleTls"] == 1: output.norm("\t\tPOODLE (TLS): No") elif ep["details"]["poodleTls"] == 2: reporter.display( "\t\tPOODLE (TLS): Vulnerable", issue.Issue(Vulnerabilities.TLS_POODLE, url, {"ip": ep["ipAddress"]}), ) else: output.error( f'\t\tPOODLE (TLS): Unknown Response ({ep["details"]["poodleTls"]})' ) else: output.error("\t\tPOODLE (TLS): Information Not Received") if "fallbackScsv" in ep["details"]: if ep["details"]["fallbackScsv"]: output.norm("\t\tDowngrade Prevention: Yes") else: reporter.display( "\t\tDowngrade Prevention: No", issue.Issue( Vulnerabilities.TLS_FALLBACK_SCSV_MISSING, url, {"ip": ep["ipAddress"]}, ), ) else: output.error("\t\tDowngrade Prevention: Information Not Received") if "compressionMethods" in ep["details"]: if ep["details"]["compressionMethods"] & 1 != 0: reporter.display( "\t\tCompression: DEFLATE", issue.Issue( Vulnerabilities.TLS_COMPRESSION_ENABLED, url, {"ip": ep["ipAddress"]}, ), ) else: output.norm("\t\tCompression: No") else: output.error("\t\tCompression: Information Not Received") if "heartbeat" in ep["details"]: if ep["details"]["heartbeat"]: reporter.display( "\t\tHeartbeat: Enabled", issue.Issue(Vulnerabilities.TLS_HEARTBEAT_ENABLED, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tHeartbeat: Disabled") else: output.error("\t\tHeartbeat: Information Not Received") if "heartbleed" in ep["details"]: if ep["details"]["heartbleed"]: reporter.display( "\t\tHeartbleed: Vulnerable", issue.Issue(Vulnerabilities.TLS_HEARTBLEEDL, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tHeartbleed: No") else: output.error("\t\tHeartbleed: Information Not Received") if "ticketbleed" in ep["details"]: if ep["details"]["ticketbleed"] == -1: output.error("\t\tTicketbleed (CVE-2016-9244): Test Failed") elif ep["details"]["ticketbleed"] == 0: output.error( "\t\tTicketbleed (CVE-2016-9244): Test Failed (Unknown)") elif ep["details"]["ticketbleed"] == 1: output.norm("\t\tTicketbleed (CVE-2016-9244): No") elif ep["details"]["ticketbleed"] == 2: reporter.display( "\t\tTicketbleed (CVE-2016-9244): Vulnerable", issue.Issue(Vulnerabilities.TLS_TICKETBLEED, url, {"ip": ep["ipAddress"]}), ) else: output.error( f'\t\tTicketbleed (CVE-2016-9244): Unknown Response ({ep["details"]["ticketbleed"]})' ) else: output.error( "\t\tTicketbleed (CVE-2016-9244): Information Not Received") if "openSslCcs" in ep["details"]: if ep["details"]["openSslCcs"] == -1: output.error("\t\tOpenSSL CCS (CVE-2014-0224): Test Failed") elif ep["details"]["openSslCcs"] == 0: output.error( "\t\tOpenSSL CCS (CVE-2014-0224): Test Failed (Unknown)") elif ep["details"]["openSslCcs"] == 1: output.norm("\t\tOpenSSL CCS (CVE-2014-0224): No") elif ep["details"]["openSslCcs"] == 2: reporter.display( "\t\tOpenSSL CCS (CVE-2014-0224): Vulnerable - Not Exploitable", issue.Issue( Vulnerabilities.TLS_OPENSSL_CVE_2014_0224_NE, url, {"ip": ep["ipAddress"]}, ), ) elif ep["details"]["openSslCcs"] == 3: output.vuln("\t\tOpenSSL CCS (CVE-2014-0224): Vulnerable") reporter.display( "\t\tOpenSSL CCS (CVE-2014-0224): Vulnerable", issue.Issue( Vulnerabilities.TLS_OPENSSL_CVE_2014_0224, url, {"ip": ep["ipAddress"]}, ), ) else: output.error( f'\t\tOpenSSL CCS (CVE-2014-0224): Unknown Response ({ep["details"]["openSslCcs"]})' ) else: output.error( "\t\tOpenSSL CCS (CVE-2014-0224): Information Not Received") if "openSSLLuckyMinus20" in ep["details"]: if ep["details"]["openSSLLuckyMinus20"] == -1: output.error( "\t\tOpenSSL Padding Oracle (CVE-2016-2107): Test Failed") elif ep["details"]["openSSLLuckyMinus20"] == 0: output.error( "\t\tOpenSSL Padding Oracle (CVE-2016-2107): Test Failed (Unknown)" ) elif ep["details"]["openSSLLuckyMinus20"] == 1: output.norm("\t\tOpenSSL Padding Oracle (CVE-2016-2107): No") elif ep["details"]["openSSLLuckyMinus20"] == 2: reporter.display( "\t\tOpenSSL Padding Oracle (CVE-2016-2107): Vulnerable", issue.Issue( Vulnerabilities.TLS_OPENSSL_CVE_2016_2107, url, {"ip": ep["ipAddress"]}, ), ) else: output.error( f"\t\tOpenSSL Padding Oracle (CVE-2016-2107): Unknown Response " f'({ep["details"]["openSSLLuckyMinus20"]})') else: output.error( "\t\tOpenSSL Padding Oracle (CVE-2016-2107): Information Not Received" ) if "bleichenbacher" in ep["details"]: if ep["details"]["bleichenbacher"] == -1: output.error("\t\tROBOT: Test Failed") elif ep["details"]["bleichenbacher"] == 0: output.error("\t\tROBOT: Test Failed (Unknown)") elif ep["details"]["bleichenbacher"] == 1: output.norm("\t\tROBOT: No") elif ep["details"]["bleichenbacher"] == 2: reporter.display( "\t\tROBOT: Vulnerable - Not Exploitable", issue.Issue(Vulnerabilities.TLS_ROBOT_ORACLE_WEAK, url, {"ip": ep["ipAddress"]}), ) elif ep["details"]["bleichenbacher"] == 3: reporter.display( "\t\tROBOT: Vulnerable - Exploitable", issue.Issue( Vulnerabilities.TLS_ROBOT_ORACLE_STRONG, url, {"ip": ep["ipAddress"]}, ), ) elif ep["details"]["bleichenbacher"] == 4: output.norm("\t\tROBOT: Unknown - Inconsistent Results") else: output.error( f'\t\tROBOT: Unknown Response ({ep["details"]["bleichenbacher"]})' ) else: output.error("\t\tROBOT: Information Not Received") if "forwardSecrecy" in ep["details"]: if ep["details"]["forwardSecrecy"] & (1 << 2) != 0: output.norm("\t\tForward Secrecy: Yes (all simulated clients)") elif ep["details"]["forwardSecrecy"] & (1 << 1) != 0: output.info("\t\tForward Secrecy: Yes (modern clients)") elif ep["details"]["forwardSecrecy"] & 1 != 0: reporter.display( "\t\tForward Secrecy: Yes (limited support)", issue.Issue( Vulnerabilities.TLS_LIMITED_FORWARD_SECRECY, url, {"ip": ep["ipAddress"]}, ), ) else: output.vuln("\t\tForward Secrecy: No") else: output.error("\t\tForward Secrecy: Information Not Received") if "supportsAead" in ep["details"]: if ep["details"]["supportsAead"]: output.norm("\t\tAEAD Cipher Suites Supported: Yes") else: reporter.display( "\t\tAEAD Cipher Suites Supported: No", issue.Issue(Vulnerabilities.TLS_NO_AEAD_SUPPORT, url, {"ip": ep["ipAddress"]}), ) else: output.error( "\t\tAEAD Cipher Suites Supported: Information Not Received") if "supportsCBC" in ep["details"]: if ep["details"]["supportsCBC"]: output.info("\t\tCBC Cipher Suites Supported: Yes") else: output.norm("\t\tCBC Cipher Suites Supported: No") else: output.error( "\t\tCBC Cipher Suites Supported: Information Not Received") if "alpnProtocols" in ep["details"]: output.norm(f'\t\tALPN: {ep["details"]["alpnProtocols"]}') if "npnProtocols" in ep["details"]: output.norm(f'\t\tNPN: {ep["details"]["npnProtocols"]}') if "sessionResumption" in ep["details"]: if ep["details"]["sessionResumption"] == 0: output.norm("\t\tSession Resumption: Not Enabled / Empty Tickets") elif ep["details"]["sessionResumption"] == 1: output.norm("\t\tSession Resumption: Enabled / No Resumption") elif ep["details"]["sessionResumption"] == 2: reporter.display( "\t\tSession Resumption: Enabled", issue.Issue( Vulnerabilities.TLS_SESSION_RESP_ENABLED, url, {"ip": ep["ipAddress"]}, ), ) else: output.error( f'\t\tSession Resumption: Unknown Response ({ep["details"]["sessionResumption"]})' ) else: output.error("\t\tSession Resumption: Information Not Received") if "ocspStapling" in ep["details"]: if ep["details"]["ocspStapling"]: output.norm("\t\tOCSP Stapling: Yes") else: reporter.display( "\t\tOCSP Stapling: No", issue.Issue( Vulnerabilities.TLS_OCSP_STAPLE_MISSING, url, {"ip": ep["ipAddress"]}, ), ) else: output.error("\t\tOCSP Stapling: Information Not Received") if "miscIntolerance" in ep["details"]: if ep["details"]["miscIntolerance"] & 1 != 0: output.info("\t\tTLS Extension Intolerance: Yes") if ep["details"]["miscIntolerance"] & (1 << 1) != 0: output.warn("\t\tLong Handshake Intolerance: Yes") if ep["details"]["miscIntolerance"] & (1 << 2) != 0: output.warn("\t\tLong Handshake Intolerance: Workaround Success") if "protocolIntolerance" in ep["details"]: if ep["details"]["protocolIntolerance"] & 1 != 0: output.warn("\t\tProtocol Intolerance: TLS 1.0") if ep["details"]["protocolIntolerance"] & (1 << 1) != 0: output.warn("\t\tProtocol Intolerance: TLS 1.1") if ep["details"]["protocolIntolerance"] & (1 << 2) != 0: output.warn("\t\tProtocol Intolerance: TLS 1.2") if ep["details"]["protocolIntolerance"] & (1 << 3) != 0: output.warn("\t\tProtocol Intolerance: TLS 1.3") if ep["details"]["protocolIntolerance"] & (1 << 4) != 0: output.warn("\t\tProtocol Intolerance: TLS 1.152") if ep["details"]["protocolIntolerance"] & (1 << 5) != 0: output.warn("\t\tProtocol Intolerance: TLS 2.152") if "freak" in ep["details"]: if ep["details"]["freak"]: reporter.display( "\t\tFREAK: Vulnerable (512-bit key exchange supported)", issue.Issue(Vulnerabilities.TLS_FREAK, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tFREAK: No") else: output.error("\t\tFREAK: Information Not Received") if "logjam" in ep["details"]: if ep["details"]["logjam"]: reporter.display( "\t\tLogjam: Vulnerable", issue.Issue(Vulnerabilities.TLS_LOGJAM, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tLogjam: No") else: output.error("\t\tLogjam: Information Not Received") if "dhUsesKnownPrimes" in ep["details"]: if ep["details"]["dhUsesKnownPrimes"] == 0: output.norm("\t\tUses common DH primes: No") elif ep["details"]["dhUsesKnownPrimes"] == 1: output.warn("\t\tUses common DH primes: Yes (not weak)") reporter.display( "\t\tUses common DH primes: Yes (weak)", issue.Issue( Vulnerabilities.TLS_DH_KNOWN_PRIMES_STRONG, url, {"ip": ep["ipAddress"]}, ), ) elif ep["details"]["dhUsesKnownPrimes"] == 2: reporter.display( "\t\tUses common DH primes: Yes (weak)", issue.Issue( Vulnerabilities.TLS_DH_KNOWN_PRIMES_WEAK, url, {"ip": ep["ipAddress"]}, ), ) else: output.error( f'\t\tUses common DH primes: Unknown Response ({ep["details"]["dhUsesKnownPrimes"]})' ) if "dhYsReuse" in ep["details"]: if ep["details"]["dhYsReuse"]: reporter.display( "\t\tDH public server param (Ys) reuse: Yes", issue.Issue(Vulnerabilities.TLS_DH_PARAM_REUSE, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tDH public server param (Ys) reuse: No") if "ecdhParameterReuse" in ep["details"]: if ep["details"]["ecdhParameterReuse"]: reporter.display( "\t\tECDH Public Server Param Reuse: Yes", issue.Issue(Vulnerabilities.TLS_ECDH_PARAM_REUSE, url, {"ip": ep["ipAddress"]}), ) else: output.norm("\t\tECDH Public Server Param Reuse: No") output.empty()
def _get_cert_info(body, ep, url): # get the ChainCert info for the server cert - needed for extra details cert = None x509_cert = None for c in body["certs"]: if c["id"] == ep["details"]["certChains"][0]["certIds"][0]: cert = c x509_cert = x509.load_pem_x509_certificate( bytes(cert["raw"], "utf_8"), default_backend()) output.norm("Certificate Information:") if cert["issues"] > 0: output.warn("\tCertificate Has Issues - Not Valid") if cert["issues"] & 1 != 0: reporter.display( "\tCertificate Issue: no chain of trust", issue.Issue(Vulnerabilities.TLS_CERT_NO_TRUST, url, {"cert": cert}), ) if cert["issues"] & (1 << 1) != 0: reporter.display( "\tCertificate Issue: certificate not yet valid", issue.Issue(Vulnerabilities.TLS_CERT_NOT_YET_VALID, url, {"cert": cert}), ) if cert["issues"] & (1 << 2) != 0: reporter.display( "\tCertificate Issue: certificate expired", issue.Issue(Vulnerabilities.TLS_CERT_EXPIRED, url, {"cert": cert}), ) if cert["issues"] & (1 << 3) != 0: reporter.display( "\tCertificate Issue: hostname mismatch", issue.Issue(Vulnerabilities.TLS_CERT_HOSTNAME_MISMATCH, url, {"cert": cert}), ) if cert["issues"] & (1 << 4) != 0: reporter.display( "\tCertificate Issue: revoked", issue.Issue(Vulnerabilities.TLS_CERT_REVOKED, url, {"cert": cert}), ) if cert["issues"] & (1 << 5) != 0: reporter.display( "\tCertificate Issue: bad common name", issue.Issue(Vulnerabilities.TLS_CERT_BAD_COMMON_NAME, url, {"cert": cert}), ) if cert["issues"] & (1 << 6) != 0: reporter.display( "\tCertificate Issue: self-signed", issue.Issue(Vulnerabilities.TLS_CERT_SELF_SIGNED, url, {"cert": cert}), ) if cert["issues"] & (1 << 7) != 0: reporter.display( "\tCertificate Issue: blacklisted", issue.Issue(Vulnerabilities.TLS_CERT_BLACKLISTED, url, {"cert": cert}), ) if cert["issues"] & (1 << 8) != 0: reporter.display( "\tCertificate Issue: insecure signature", issue.Issue(Vulnerabilities.TLS_CERT_INSECURE_SIGNATURE, url, {"cert": cert}), ) if cert["issues"] & (1 << 9) != 0: reporter.display( "\tCertificate Issue: insecure key", issue.Issue(Vulnerabilities.TLS_CERT_INSECURE_KEY, url, {"cert": cert}), ) output.empty() output.norm(f'\tSubject: {cert["subject"]}') output.norm(f'\tCommon Names: {" ".join(cert["commonNames"])}') output.norm("\tAlternative names:") for name in cert["altNames"]: output.norm(f"\t\t{name}") output.norm(f'\tNot Before: {x509_cert.not_valid_before.isoformat(" ")}') output.norm(f'\tNot After: {x509_cert.not_valid_after.isoformat(" ")}') if cert["keyAlg"] == "EC": output.norm( f'\tKey: {cert["keyAlg"]} {cert["keySize"]} (RSA equivalent: {cert["keyStrength"]})' ) else: if cert["keySize"] < 2048: output.vuln(f'\tKey: {cert["keyAlg"]} {cert["keySize"]}') else: output.norm(f'\tKey: {cert["keyAlg"]} {cert["keySize"]}') # TODO: Public Key Hash serial = format(x509_cert.serial_number, "02x") output.norm(f"\tSerial: {serial}") output.norm(f'\tIssuer: {cert["issuerSubject"]}') if "validationType" in cert: if cert["validationType"] == "E": output.norm("\tExtended Validation: Yes") elif cert["validationType"] == "D": output.norm("\tExtended Validation: No (Domain Control)") else: output.norm( f'\tExtended Validation: No ({cert["validationType"]})') if cert["sct"]: # check the first bit, SCT in cert if ep["details"]["hasSct"] & 1 != 0: output.norm("\tCertificate Transparency: SCT in certificate") # check second bit, SCT in stapled OSCP response if ep["details"]["hasSct"] & (1 << 1) != 0: output.norm( "\tCertificate Transparency: SCT in the stapled OCSP response") # check third bit, SCT in the TLS extension if ep["details"]["hasSct"] & (1 << 2) != 0: output.norm( "\tCertificate Transparency: SCT in the TLS extension (ServerHello)" ) else: output.norm("\tCertificate Transparency: No") output.norm(f'\tOCSP Must Staple: {cert["mustStaple"]}') if cert["revocationInfo"] & 1 != 0: output.norm("\tRevocation information: CRL information available") if cert["revocationInfo"] & (1 << 1) != 0: output.norm("\tRevocation information: OCSP information available") if cert["revocationStatus"] == 0: output.norm("\tRevocation Status: not checked") elif cert["revocationStatus"] == 1: output.vuln('\tRevocation Status: certificate revoked"') elif cert["revocationStatus"] == 2: output.norm("\tRevocation Status: certificate not revoked") elif cert["revocationStatus"] == 3: output.warn("\tRevocation Status: revocation check error") elif cert["revocationStatus"] == 4: output.warn("\tRevocation Status: no revocation information") elif cert["revocationStatus"] == 5: output.error("\tRevocation Status: SSL Labs internal error") else: output.error( f'\tRevocation Status: Unknown response ({cert["revocationStatus"]})' ) if "crlRevocationStatus" in cert: if cert["crlRevocationStatus"] == 0: output.norm("\tCRL Revocation Status: not checked") elif cert["crlRevocationStatus"] == 1: output.vuln('\tCRL Revocation Status: certificate revoked"') elif cert["crlRevocationStatus"] == 2: output.norm("\tCRL Revocation Status: certificate not revoked") elif cert["crlRevocationStatus"] == 3: output.warn("\tCRL Revocation Status: revocation check error") elif cert["crlRevocationStatus"] == 4: output.warn("\tCRL Revocation Status: no revocation information") elif cert["crlRevocationStatus"] == 5: output.error("\tCRL Revocation Status: SSL Labs internal error") else: output.error( f'\tCRL Revocation Status: Unknown response ({cert["crlRevocationStatus"]})' ) else: output.norm("\tCRL Revocation Status: Not Provided") if "ocspRevocationStatus" in cert: if cert["ocspRevocationStatus"] == 0: output.norm("\tOCSP Revocation Status: not checked") elif cert["ocspRevocationStatus"] == 1: output.vuln('\tOCSP Revocation Status: certificate revoked"') elif cert["ocspRevocationStatus"] == 2: output.norm("\tOCSP Revocation Status: certificate not revoked") elif cert["ocspRevocationStatus"] == 3: output.warn("\tOCSP Revocation Status: revocation check error") elif cert["ocspRevocationStatus"] == 4: output.warn("\tOCSP Revocation Status: no revocation information") elif cert["ocspRevocationStatus"] == 5: output.error("\tOCSP Revocation Status: SSL Labs internal error") else: output.error( f'\tOCSP Revocation Status: Unknown response ({cert["ocspRevocationStatus"]})' ) else: output.norm("\tOCSP Revocation Status: Not Provided") output.empty() exts = cert_info.format_extensions(x509_cert) for ext in exts: output.norm(f"\tExtensions: {ext}") output.empty() scts = cert_info.get_scts(x509_cert) for sct in scts: output.norm( f'\tSCT: {cert_info.get_ct_log_name(sct[1])} - {sct[2].isoformat(" ")}' ) output.empty() cert_hash = bytes.hex(x509_cert.fingerprint(hashes.SHA1())) output.norm(f"\tFingerprint: {cert_hash}") output.norm(f"\t\thttps://censys.io/certificates?q={cert_hash}") output.norm(f"\t\thttps://crt.sh/?q={cert_hash}") output.empty() output.norm("\tCertificate Chains:") for chain in ep["details"]["certChains"]: path_count = 0 # build list of trust paths trust_paths = {} for path in chain["trustPaths"]: trusts = None # in practice, it seems there is only only per path, but just in case for trust in path["trust"]: if trust["isTrusted"]: trust_line = f'{trust["rootStore"]} (trusted)' else: trust_line = f'{trust["rootStore"]} ({trust["trustErrorMessage"]})' if trusts is None: trusts = trust_line else: trusts += f" {trust_line}" if trust_paths.get(tuple(path["certIds"])) is not None: trust_paths[tuple(path["certIds"])] += f" {trusts}" else: trust_paths[tuple(path["certIds"])] = trusts # process each of the trust paths for key in trust_paths.keys(): path_count += 1 output.norm(f"\t Path {path_count}:") output.norm(f"\t Root Stores: {trust_paths[key]}") if chain["issues"] & (1 << 1) != 0: output.warn("\tCertificate Chain Issue: incomplete chain") if chain["issues"] & (1 << 2) != 0: output.warn( "\tCertificate Chain Issue: chain contains unrelated/duplicate certificates" ) if chain["issues"] & (1 << 3) != 0: output.warn("\tCertificate Chain Issue: incorrect order") if chain["issues"] & (1 << 4) != 0: output.warn("\tCertificate Chain Issue: contains anchor") if cert["issues"] & (1 << 5) != 0: output.warn("\tCertificate Chain Issue: untrusted") for path_cert in key: for c in body["certs"]: if c["id"] == path_cert: output.norm(f'\t\t{c["subject"]}') output.norm( f'\t\t Signature: {c["sigAlg"]} Key: {c["keyAlg"]}-{c["keySize"]}' ) if cert_info.check_symantec_root(c["sha256Hash"]): reporter.display( "\t\t Untrusted Symantec Root", issue.Issue( Vulnerabilities.TLS_SYMANTEC_ROOT, url, {"fingerprint": c["sha1Hash"]}, ), ) output.norm(f'\t\t https://crt.sh/?q={c["sha1Hash"]}') if c["sha256Hash"] not in chain["certIds"]: output.norm("\t\t (provided by server)") output.empty()