Source code for ssllabs.endpointdetails

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright © 2016 Taylor C. Richberger <taywee@gmx.com>
# This code is released under the license described in the LICENSE file

from __future__ import division, absolute_import, print_function, unicode_literals

from datetime import datetime, timedelta
import codecs

from ssllabs.cert import Cert
from ssllabs.chain import Chain
from ssllabs.drownhost import DrownHost
from ssllabs.hpkppolicy import HpkpPolicy
from ssllabs.hstspolicy import HstsPolicy
from ssllabs.hstspreload import HstsPreload
from ssllabs.key import Key
from ssllabs.object import Object
from ssllabs.protocol import Protocol
from ssllabs.simdetails import SimDetails
from ssllabs.suites import Suites
from ssllabs.util import objectornone

[docs]class EndpointDetails(Object): '''Detailed information about an endpoint, accessed from :meth:`ssllabs.endpoint.Endpoint.details`''' def __init__(self, data): self.__hostStartTime = datetime.utcfromtimestamp(0) + timedelta(milliseconds=data['hostStartTime']) if 'hostStartTime' in data else None self.__key = objectornone(Key, data, 'key') self.__cert = objectornone(Cert, data, 'cert') self.__chain = objectornone(Chain, data, 'chain') self.__protocols = [Protocol(protocol) for protocol in data.get('protocols', list())] self.__suites = objectornone(Suites, data, 'suites') self.__serverSignature = data.get('serverSignature') self.__prefixDelegation = data.get('prefixDelegation') self.__nonPrefixDelegation = data.get('nonPrefixDelegation') self.__vulnBeast = data.get('vulnBeast') self.__renegSupport = objectornone(RenegSupport, data, 'renegSupport') self.__sessionResumption = data.get('sessionResumption') self.__compressionMethods = objectornone(CompressionMethods, data, 'compressionMethods') self.__supportsNpn = data.get('supportsNpn') self.__npnProtocols = data.get('npnProtocols', '').split(' ') self.__sessionTickets = objectornone(SessionTickets, data, 'sessionTickets') self.__ocspStapling = data.get('ocspStapling') self.__staplingRevocationStatus = data.get('staplingRevocationStatus') self.__staplingRevocationErrorMessage = data.get('staplingRevocationErrorMessage') self.__sniRequired = data.get('sniRequired') self.__httpStatusCode = data.get('httpStatusCode') self.__httpForwarding = data.get('httpForwarding') self.__supportsRc4 = data.get('supportsRc4') self.__rc4WithModern = data.get('rc4WithModern') self.__rc4Only = data.get('rc4Only') self.__forwardSecrecy = objectornone(ForwardSecrecy, data, 'forwardSecrecy') self.__protocolIntolerance = objectornone(ProtocolIntolerance, data, 'protocolIntolerance') self.__miscIntolerance = objectornone(MiscIntolerance, data, 'miscIntolerance') self.__sims = objectornone(SimDetails, data, 'sims') self.__heartbleed = data.get('heartbleed') self.__heartbeat = data.get('heartbeat') self.__openSslCcs = data.get('openSslCcs') self.__openSSLLuckyMinus20 = data.get('openSSLLuckyMinus20') self.__poodle = data.get('poodle') self.__poodleTls = data.get('poodleTls') self.__fallbackScsv = data.get('fallbackScsv') self.__freak = data.get('freak') self.__hasSct = objectornone(HasSct, data, 'hasSct') self.__dhPrimes = [codecs.decode(prime, 'hex_codec') for prime in data.get('dhPrimes', list())] self.__dhUsesKnownPrimes = data.get('dhUsesKnownPrimes') self.__dhYsReuse = data.get('dhYsReuse') self.__logjam = data.get('logjam') self.__chaCha20Preference = data.get('chaCha20Preference') self.__hstsPolicy = objectornone(HstsPolicy, data, 'hstsPolicy') self.__hstsPreloads = [HstsPreload(preload) for preload in data.get('hstsPreloads', list())] self.__hpkpPolicy = objectornone(HpkpPolicy, data, 'hpkpPolicy') self.__hpkpRoPolicy = objectornone(HpkpPolicy, data, 'hpkpRoPolicy') self.__drownHosts = [DrownHost(host) for host in data.get('drownHosts', list())] self.__drownErrors = data.get('drownErrors') self.__drownVulnerable = data.get('drownVulnerable') @property def hostStartTime(self): '''endpoint assessment starting time, in milliseconds since 1970. This field is useful when test results are retrieved in several HTTP invocations. Then, you should check that the hostStartTime value matches the startTime value of the host.''' return self.__hostStartTime @property def key(self): '''key information, as a :class:`ssllabs.key.Key`''' return self.__key @property def cert(self): '''certificate information as a :class:`ssllabs.cert.Cert`''' return self.__cert @property def chain(self): '''chain information, as a :class:`ssllabs.chain.Chain`''' return self.__chain @property def protocols(self): '''supported protocols, as a list of :class:`ssllabs.protocol.Protocol`''' return self.__protocols @property def suites(self): '''supported cipher suites, as a :class:`ssllabs.suites.Suites`''' return self.__suites @property def serverSignature(self): '''Contents of the HTTP Server response header when known. This field could be absent for one of two reasons: 1) the HTTP request failed (check httpStatusCode) or 2) there was no Server response header returned.''' return self.__serverSignature @property def prefixDelegation(self): '''true if this endpoint is reachable via a hostname with the www prefix''' return self.__prefixDelegation @property def nonPrefixDelegation(self): '''true if this endpoint is reachable via a hostname without the www prefix''' return self.__nonPrefixDelegation @property def vulnBeast(self): '''true if the endpoint is vulnerable to the BEAST attack''' return self.__vulnBeast @property def renegSupport(self): '''this is :class:`RenegSupport` object that describes the endpoint support for renegotiation''' return self.__renegSupport @property def sessionResumption(self): '''this is an integer value that describes endpoint support for session resumption. The possible values are: 0 - session resumption is not enabled and we're seeing empty session IDs 1 - endpoint returns session IDs, but sessions are not resumed 2 - session resumption is enabled''' return self.__sessionResumption @property def compressionMethods(self): '''integer value that describes supported compression methods, as a :class:`CompressionMethods`''' return self.__compressionMethods @property def supportsNpn(self): '''true if the server supports NPN''' return self.__supportsNpn @property def npnProtocols(self): '''list of supported protocols''' return self.__npnProtocols @property def sessionTickets(self): '''indicates support for Session Tickets, as a :class:`SessionTickets` object''' return self.__sessionTickets @property def ocspStapling(self): '''true if OCSP stapling is deployed on the server''' return self.__ocspStapling @property def staplingRevocationStatus(self): '''same as :meth:`ssllabs.cert.Cert.revocationStatus`, but for the stapled OCSP response.''' return self.__staplingRevocationStatus @property def staplingRevocationErrorMessage(self): '''description of the problem with the stapled OCSP response, if any.''' return self.__staplingRevocationErrorMessage @property def sniRequired(self): '''if SNI support is required to access the web site.''' return self.__sniRequired @property def httpStatusCode(self): '''status code of the final HTTP response seen. When submitting HTTP requests, redirections are followed, but only if they lead to the same hostname. If this field is not available, that means the HTTP request failed.''' return self.__httpStatusCode @property def httpForwarding(self): '''available on a server that responded with a redirection to some other hostname.''' return self.__httpForwarding @property def supportsRc4(self): '''true if the server supports at least one RC4 suite.''' return self.__supportsRc4 @property def rc4WithModern(self): '''true if RC4 is used with modern clients.''' return self.__rc4WithModern @property def rc4Only(self): '''true if only RC4 suites are supported.''' return self.__rc4Only @property def forwardSecrecy(self): '''indicates support for Forward Secrecy, as a :class:`ForwardSecrecy` object''' return self.__forwardSecrecy @property def protocolIntolerance(self): '''indicates protocol version intolerance issues as :class:`ProtocolIntolerance`''' return self.__protocolIntolerance @property def miscIntolerance(self): '''indicates various other types of intolerance as :class:`MiscIntolerance`''' return self.__miscIntolerance @property def sims(self): '''instance of SimDetails.''' return self.__sims @property def heartbleed(self): '''true if the server is vulnerable to the Heartbleed attack.''' return self.__heartbleed @property def heartbeat(self): '''true if the server supports the Heartbeat extension.''' return self.__heartbeat @property def openSslCcs(self): '''results of the CVE-2014-0224 test: -1 - test failed 0 - unknown 1 - not vulnerable 2 - possibly vulnerable, but not exploitable 3 - vulnerable and exploitable''' return self.__openSslCcs @property def openSSLLuckyMinus20(self): '''results of the CVE-2016-2107 test: -1 - test failed 0 - unknown 1 - not vulnerable 2 - vulnerable and insecure''' return self.__openSSLLuckyMinus20 @property def poodle(self): '''true if the endpoint is vulnerable to POODLE; false otherwise''' return self.__poodle @property def poodleTls(self): '''results of the POODLE TLS test: -3 - timeout -2 - TLS not supported -1 - test failed 0 - unknown 1 - not vulnerable 2 - vulnerable''' return self.__poodleTls @property def fallbackScsv(self): '''true if the server supports TLS_FALLBACK_SCSV, false if it doesn't. This field will not be available if the server's support for TLS_FALLBACK_SCSV can't be tested because it supports only one protocol version (e.g., only TLS 1.2).''' return self.__fallbackScsv @property def freak(self): '''true of the server is vulnerable to the FREAK attack, meaning it supports 512-bit key exchange.''' return self.__freak @property def hasSct(self): '''information about the availability of certificate transparency information (embedded SCTs) as :class:`HasSct`''' return self.__hasSct @property def dhPrimes(self): '''list of DH primes used by the server (as raw binary bytes objects). Not present if the server doesn't support the DH key exchange.''' return self.__dhPrimes @property def dhUsesKnownPrimes(self): '''whether the server uses known DH primes. Not present if the server doesn't support the DH key exchange. Possible values: 0 - no 1 - yes, but they're not weak 2 - yes and they're weak''' return self.__dhUsesKnownPrimes @property def dhYsReuse(self): '''true if the DH ephemeral server value is reused. Not present if the server doesn't support the DH key exchange.''' return self.__dhYsReuse @property def logjam(self): '''true if the server uses DH parameters weaker than 1024 bits.''' return self.__logjam @property def chaCha20Preference(self): '''true if the server takes into account client preferences when deciding if to use ChaCha20 suites.''' return self.__chaCha20Preference @property def hstsPolicy(self): '''server's HSTS policy as a :class:`ssllabs.hstspolicy.HstsPolicy`. Experimental.''' return self.__hstsPolicy @property def hstsPreloads(self): '''information about preloaded HSTS policies as a list of :class:`ssllabs.hstspreload.HstsPreload`''' return self.__hstsPreloads @property def hpkpPolicy(self): '''server's HPKP policy as a :class:`ssllabs.hpkppolicy.HpkpPolicy`. Experimental.''' return self.__hpkpPolicy @property def hpkpRoPolicy(self): '''server's HPKP RO (Report Only) policy as a :class:`ssllabs.hpkppolicy.HpkpPolicy`. Experimental.''' return self.__hpkpRoPolicy @property def drownHosts(self): '''list of drown hosts as :class:`ssllabs.drownhost.DrownHost`. Experimental.''' return self.__drownHosts @property def drownErrors(self): '''true if error occurred in drown test.''' return self.__drownErrors @property def drownVulnerable(self): '''true if server vulnerable to drown attack.''' return self.__drownVulnerable
[docs]class RenegSupport(object): '''support for renegotiation, from :meth:`EndpointDetails.renegSupport`''' def __init__(self, data): self.__clientinitiated = bool(1 & data) self.__secure = bool(2 & data) self.__secureclientinitiated = bool(4 & data) self.__serverrequiressecure = bool(8 & data) @property def clientinitiated(self): '''set if insecure client-initiated renegotiation is supported''' return self.__clientinitiated @property def secure(self): '''set if secure renegotiation is supported''' return self.__secure @property def secureclientinitiated(self): '''set if secure client-initiated renegotiation is supported''' return self.__secureclientinitiated @property def serverrequiressecure(self): '''set if the server requires secure renegotiation support''' return self.__serverrequiressecure
[docs]class CompressionMethods(object): '''supported compression methods, from :meth:`EndpointDetails.compressionMethods`''' def __init__(self, data): self.__deflate = bool(1 & data) @property def deflate(self): '''set for DEFLATE''' return self.__deflate
[docs]class SessionTickets(object): '''support for session tickets, from :meth:`EndpointDetails.sessionTickets`''' def __init__(self, data): self.__supported = bool(1 & data) self.__faulty = bool(2 & data) self.__intolerant = bool(4 & data) @property def supported(self): '''set if session tickets are supported''' return self.__supported @property def faulty(self): '''set if the implementation is faulty [not implemented]''' return self.__faulty @property def intolerant(self): '''set if the server is intolerant to the extension''' return self.__intolerant
[docs]class ForwardSecrecy(object): '''indicates support for Forward Secrecy, from :meth:`EndpointDetails.forwardSecrecy`''' def __init__(self, data): self.__negotiated = bool(1 & data) self.__modernacheived = bool(2 & data) self.__allacheived = bool(4 & data) @property def negotiated(self): '''set if at least one browser from our simulations negotiated a Forward Secrecy suite''' return self.__negotiated @property def modernacheived(self): '''set based on Simulator results if FS is achieved with modern clients. For example, the server supports ECDHE suites, but not DHE''' return self.__modernacheived @property def allacheived(self): '''set if all simulated clients achieve FS. In other words, this requires an ECDHE + DHE combination to be supported''' return self.__allacheived
[docs]class ProtocolIntolerance(object): '''indicates protocol version intolerance issues, from :meth:`EndpointDetails.protocolIntolerance`''' def __init__(self, data): self.__TLS_1_0 = bool(1 & data) self.__TLS_1_1 = bool(2 & data) self.__TLS_1_2 = bool(4 & data) self.__TLS_1_3 = bool(8 & data) self.__TLS_1_152 = bool(16 & data) self.__TLS_2_152 = bool(32 & data) @property def TLS_1_0(self): '''TLS 1.0''' return self.__TLS_1_0 @property def TLS_1_1(self): '''TLS 1.1''' return self.__TLS_1_1 @property def TLS_1_2(self): '''TLS 1.2''' return self.__TLS_1_2 @property def TLS_1_3(self): '''TLS 1.3''' return self.__TLS_1_3 @property def TLS_1_152(self): '''TLS 1.152''' return self.__TLS_1_152 @property def TLS_2_152(self): '''TLS 2.152''' return self.__TLS_2_152
[docs]class MiscIntolerance(object): '''indicates various other types of intolerance, from :meth:`EndpointDetails.miscIntolerance`''' def __init__(self, data): self.__extensionintolerance = bool(1 & data) self.__longhandshakeintolerance = bool(2 & data) self.__longhandshakeworkaround = bool(4 & data) @property def extensionintolerance(self): '''extension intolerance''' return self.__extensionintolerance @property def longhandshakeintolerance(self): '''long handshake intolerance''' return self.__longhandshakeintolerance @property def longhandshakeworkaround(self): '''long handshake intolerance workaround success''' return self.__longhandshakeworkaround
[docs]class HasSct(object): '''information about the availability of certificate transparency information (embedded SCTs), from :meth:`EndpointDetails.hasSct`''' def __init__(self, data): self.__sctincertificate = bool(1 & data) self.__sctinstapledocsp = bool(2 & data) self.__sctintlsextension = bool(4 & data) @property def sctincertificate(self): '''SCT in certificate''' return self.__sctincertificate @property def sctinstapledocsp(self): '''SCT in the stapled OCSP response''' return self.__sctinstapledocsp @property def sctintlsextension(self): '''SCT in the TLS extension (ServerHello)''' return self.__sctintlsextension